Java基础知识点简记

  此篇主要记录(搬运)的是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中重载和覆盖(又称重写)的区别]

[参考2:java中重载与重写的区别]

组合和继承的理解

  1. 在继承结构中,父类的内部细节对于子类是可见的。所以我们通常也可以说通过继承的代码复用是一种白盒式代码复用。(如果基类的实现发生改变,那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性;)
  2. 组合是通过对现有的对象进行拼装(组合)产生新的、更复杂的功能。因为在对象之间,各自的内部细节是不可见的,所以我们也说这种方式的代码复用是黑盒式代码复用。(因为组合中一般都定义一个类型,所以在编译期根本不知道具体会调用哪个实现类的方法)
  3. 继承,在写代码的时候就要指名具体继承哪个类,所以,在编译期就确定了关系。(从基类继承来的实现是无法在运行期动态改变的,因此降低了应用的灵活性。)
  4. 组合,在写代码的时候可以采用面向接口编程。所以,类的组合关系一般在运行期确定。
组合关系 继承关系
优点:不破坏封装,整体类与局部类之间松耦合,彼此相对独立 缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性
优点:具有较好的可扩展性 缺点:支持扩展,但是往往以增加系统结构的复杂度为代价
优点:支持动态组合。在运行时,整体对象可以选择不同类型的局部对象 缺点:不支持动态继承。在运行时,子类无法选择不同的父类
优点:整体类可以对局部类进行包装,封装局部类的接口,提供新的接口 缺点:子类不能改变父类的接口
缺点:整体类不能自动获得和局部类同样的接口 优点:子类能自动继承父类的接口
缺点:创建整体类的对象时,需要创建所有局部类的对象 优点:创建子类的对象时,无须创建父类的对象

抽象类abstract class与接口interface的理解

  广义上的抽象类(Java中包括抽象类或者接口)往往用来表征我们在对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。正是因为抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能够实例化的.

  接口可以继承接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类。

  OCP原则(Open-Closed Principle)在java中的体现很大程度上就是接口和抽象类的设计,即抽象化设计。

  1. 抽象类在Java语言中表示的是一种继承关系,Java中是单继承(普通类和抽象类)和多实现(接口)。
  2. 在抽象类中可以有自己的数据成员,数据成员的值可以重新定义或者赋值,也可以有非abstarct的成员方法,即可以有具体的方法;而在接口中,只能够有静态的不能被修改的数据成员(也即static final,而且必须有初始值,不过在接口中一般不定义数据成员),而且所有的成员方法默认都是public abstract。
  3. 实现抽象类和接口的类必须实现其中的所有抽象方法。抽象类中可以有非抽象方法。接口中则不能有实现方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
  4. 接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法
  5. 接口的方法都是public的,抽象类的方法可以是public,protected,private或者默认的package,抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
  6. 抽象类可以定义构造函数,接口却不能。
  7. 抽象类表示对事物(属性和行为)的抽象,即对类的抽象,而接口是对行为的抽象,是一种行为规范或者行为约定。
  8. 抽象类和接口所反映出的设计理念不同。其实抽象类表示的是"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中抽象类和接口的区别]

[参考2:Java抽象类与接口的区别]

静态嵌套类(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 == 1.0;//成立
2 
3 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()方法详解]

常量池、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常量池理解与总结]  

 [参考2:Java中几种常量池的区分]

当然,重点去参考《深入理解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的区别]

[参考2:Java:String、StringBuffer和StringBuilder的区别]

对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和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。

静态方法是否能被重写

  属于类,不能被继承,不能重写。

猜你喜欢

转载自www.cnblogs.com/wpbxin/p/8975096.html