《Think in Java》笔记I
5.2 区分重载方法:
每个重载方法都会有一个独一无二的参数类型列表,当然也可以已返回值区分重载方法。
5.5.4 Java虚拟机采用的是一种自适应的垃圾回收技术
有一种名为停止-复制,即先暂停程序的运行(所以它不属于后台回收模式),然后将所有存活的的对象从当前堆复制到另一个堆,没有复制的就是垃圾了。当对象被复制到新堆时,它们是一个挨着一个以新堆保持紧凑排列,然后就可以按前述方法简单、直接的分配内存了。
标记-清扫模式依据的思路同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象。每当找到一个存活的对象,就给对象设一个标记,这个过程不会回收任何对象。只有当全部标记工作完成的时候,清理工作才会开始。在清理过程中,没有标记的对象将被释放,不会发生任何复制工作。所以剩下的堆空间是不连续的,垃圾回收期要是希望得到连续空间的话,就得重新整理剩下的对象。
5.7 初始化顺序:
在类的内部,变量定义的先后顺序决定了初始化的顺序。静态初始化只有在必要时刻才会进行,此后,初始化对象不会再被初始化。
第六章笔记:
6.2 Java访问权限修饰词:
public:公开,都能访问; private:除了包含该成员的类之外,其他任何类都无法访问 ;protected:继承访问权限,主要体现在:
1.基类的protected成员是包内可见的,并且对子类可见
2.若子类与基类不在同一个包中,那么在子类中,子类实例可以访问从基类继承来的protected方法,而不能访问基类实例的protected方法。
第七章:复用类(组合与继承)
7.1 编译器并不是简单地为每一个引用都创建默认对象,这一点是很有意义的,因为若真要那样做的话,就会在许多情况下增加不必要的负担。如果想初始化这些引用,可以在代码中的下列位置进行:
-
在定义对象的地方。这意味着他们总是能够在构造器被调用之前被初始化。
-
在类的构造器中。
-
就在正要使用这些对象之前,这种方式被称为惰性初始化。在生成对象不值得及不必每次都生成对象的情况下,这种方式可以减少额外的负担。
-
使用实例初始化。
7.2 继承语法:(组合和继承)
继承是所有OOP语言和Java语言不可缺少的组成部分。当创建一个类时,总是在继承,因此,除非已明确指出要从其他类中继承,否则就是在隐式的从Java的标准根类Object进行继承。
组合的语法比较平实,但是继承使用的是一种特殊的语法。在继承的过程中需要先声明“新类与旧类相似”。这种声明是通过在类主题的左边花括号之前,回溯斜后面紧随基类名称的关键字extends而实现的,当这么做时,会自动得道基类中的所有域和方法。
以下部分转载自kk少年的https://www.jianshu.com/p/d345d1bc7e4a
继承的特点
-
子类通过关键字extends 实现对父类的继承
-
子类只可继承来自父类的除私有的属性和方法,对于包访问权限的属性和方法只能被同个包内的子类继承。
-
构造方法不能被继承。
-
静态方法和静态变量可以被继承。
-
java中,类之间只可单继承,即一个类只能继承一个父类。
-
接口亦可继承另一个接口,但是接口可以多继承。
-
继承的变量和方法可以覆盖。
-
方法重写不允许降低访问权限。
-
继承是紧耦合的。
另外,静态方法不能重写,因为重写指的是根据运行时对象的类型来决定调用哪个方法,而不是编译时的,类静态方法是编译时确定的,即使你在子类中定义了一个和父类一样的静态方法,编译器也不会报错,从多态的角度看,这并不是对静态方法的重写,而是子类自己的方法。
继承:
优点:
(1) 子类自动继承父类接口,在多态时很方便 (2) 创建子类时无需创建父类对象
缺点:
(1) 继承破坏封装性
给父类增加了一个方法A,这时子类与父类之间就可能越来越脱离is-a 举个例子:比如,鸟类有羽毛等属性,这里有一个需求是,定义一个有羽毛的鸡类,采用继承的方法很优雅也很方便,直接一个extends 就可以实现,但是如果有一天,这个鸟类添加了一个飞翔的公有方法,此前继承了鸟类的鸡类会自动继承了这个方法,鸡会飞翔?顶多就是矮距离飞跃。此时给鸡飞的方法就是破坏了鸡的封装性,鸡不应该有此方法。此时的鸡已经和有飞翔行为的鸟类之间不是is-a 关系了。
(2) 继承是紧耦合:
继承紧耦合体现在父类变就会影响子类,此时子类如果因此需要修改,重构的难度可能会很高。
(3) 子类对父类的扩展往往会增加系统结构复杂度
继承树深度加深,结构越复杂。
(4) 不支持在运行时指定父类
(5) 子类不能改变父类的接口
组合
什么是组合?给个代码
public class A{ public void a1(){} public void a2(){} } public class B{ private A a = new A(); public void a1(){ a.a1(); } public void a2(){ a.a2(); } }
其中B类对A类这种复用的形式就是组合,这个是通过包装和方法转发实现的。
接下来讲述组合优缺点
优点
-
组合不破坏封装,相对于继承
-
组合松耦合,包装类和被包装类彼此独立,不会因为被包装类突然加个方法就使得包装类多了一个方法,包装类视情况包装所需方法。
-
支持动态组合,组合的方式在运行时可以根据条件来选择所组合的类。
-
包装类可以通过包装改变被包装类的接口,比如被包装类是实现了Set接口的,我可以通过包装,让包装类实现Map接口。
缺点
-
不能实现多态
-
无法自动获得被包装类的接口,比如被包装类实现了Set接口,包装类并没有自动获得此接口,需要经过包装,才有可能和他一样的接口。
何时用继承,何时用组合
这应该才是我们关心的问题吧。 在以下几种情况使用组合:
-
子类只需要继承父类的一部分,继承就没辙了。
-
如果只是为了具有父类的一些属性方法,比如汽车具有轮胎和发动引擎,但是如果为此继承这两个类是很不明智的,使用组合更为恰当。
-
如果设计的子类是为了复用代码,并不是为了扩展父类,那么最好是选组合的方式,因为父类改变会影响子类。对于只是为了复用而继承的类很不利。
什么时候使用继承?
-
类之间很明显是一种is-a 关系,而不是has-a或者contain-a关系。
-
考虑多态时使用继承
另外:
7.4 结合使用组成和继承
同时使用组合和集成式很常见的事。下例就展示了同时使用这两种技术,并配以必要的构造器初始化,来创建更复杂的类
``
package Mypackage; import java.io.*; import java.sql.SQLOutput; import java.util.*; class Plate { Plate(int i){ System.out.println("Plste constructor"); } } class DinnerPlate extends Plate{ DinnerPlate(int i){ super(i); System.out.println("DinnerPlate constructor"); } } class Utensil{ Utensil(int i){ System.out.println("Utensil constructor"); } } class Spoon extends Utensil{ Spoon(int i){ super(i); System.out.println("Spoon constructor"); } } class Fork extends Utensil{ Fork(int i){ super(i); System.out.println("Fork constructor"); } } class Knife extends Utensil{ Knife(int i){ super(i); System.out.println("Knife constructor"); } } class Custom{ Custom(int i){ System.out.println("Custom constructor"); } } public class Study extends Custom{ private Spoon sp; private Fork frk; private Knife kn; private DinnerPlate pl; Study(int i){ super(i+1); sp = new Spoon(i+2); frk = new Fork(i+3); kn = new Knife(i+4); pl = new DinnerPlate(i+5); System.out.println("PlaceSetting constructor"); } public static void main(String[] args){ Study st = new Study(9); } }
7.8.1 空白final
Java允许生成空白final,所谓空白final是指被声明为final但又为给定初值的域。无论什么情况,编译器都确保空白final在使用前必须被初始化。但是,空白final在关键字final的使用上提供了更大的灵活性,为此,一个类中的final域就可以做到根据对象而有所不同,却又保持恒定不变的特性。
final和private关键字: 类中所有的private方法都隐式的指定是final的,可以对private方法添加final修饰词,但这并不能给方法增加任何额外的意义。
总结:
继承和组合都能从现有类型生成新类型。组成一般是将现有类型作为新类型底层实现的一部分来加以复用,耳继承复用的是接口。
在使用继承时,由于导出类具有基类接口,因此它可以向上转型直基类,这对多态来说至关重要。
尽管面向对象编程对继承极力强调,但在开始一个设计时,一般应优先选择使用组合(或者代理),旨在确实必要时才使用继承。应为组合更具灵活性。此外,通过对成员类型使用继承技术的添加技巧,可以在运行时改变那些成员对象的类型和行为。因此,可以在运行时改变组合而成的对象的行为。
8.2.5:
只有普通的方法是可以调动多态的,如果某个方法是静态的,它的行为就不具有多态性。
8.3.1:构造器调用的顺序:
1)调用基类构造器。这个步骤会不断的反复递归下去,首先是构造这种层次结构的根,然后是下一层导出类,等等,直到最底层的导出类。
2)按声明顺序调用成员的初始化方法。
3)调用导出类构造器的主体。
第九章笔记:接口
9.2:接口
abstract关键字允许人们在类中创建一个或多个没有任何定义的方法——提供了接口部分,但是没有提供任何相应的具体实现,这些实现是由此类的继承者创建的。interface这个关键字产生一个完全抽象的类,它根本就没有提供任何具体实现。它允许创建者确定方法名、参数列表和返回类型,但是没有任何方法体。
第十章笔记:内部类
10.2 链接到外部类
当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。
10.3 使用.this和.new
有时你可能想要告知某些其他对象,去创建起某个内部类的对象。要实现此目的,你必须在new表达式中提供对其他外部类对象的引用,这是需要使用.new语法,就像下面这样:
public class DotNew{ public class Inner{} public static void main(String[] args){ DotNew dn = new DotNew(); DotNew.Inner dni = dn.new Inner(); } }
10.6 匿名内部类
public class Parcel7{ public Contents contents(){ return new Contents(){ private int i = 11; public int value(){ return i; } }; } public static void main(String[] args){ Parcel7 p = new Parcel7(); Contents c = p.contents(); } }
contents()方法将返回值的生成与表示这个返回值的类的定义结合在一起!另外,这个类是匿名的。这种奇怪的语法指的是:“创建一个继承自Contents的匿名类对象”。通过new表达式返回的引用被自动向上转型为对Contents的应用。
import static net.mindview.util.Print.*; abstract class Base{ public Base(int i){ print("Base constructor,i = " + i); } public abstract void f(); } public class AnonymousConstructor{ public static Base getBase(int i){ return new Base(i){ { print("Inside instance initializer"); } public void f(){ print("In anonymous f()"); } }; } public static void main(String args){ Base base = getBase(47); base.f(); } } /*output *Base constructor , i = 47 *Inside instance initializer *In anonymous f() */
在此例中,不要求变量i一定是final的。因为i被传递给匿名类的基类构造器,它并不会在匿名内部类内部被直接使用。
如果在匿名类内部使用的,方法签名中的参数必须是final的,如destination(final String dest, final float price){}
10.7 嵌套类
如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。这通常称为嵌套类。想要理解static应用于内部类时的含义,就必须记住,普通的内部类对象隐式的保存了一个引用,只想创建它的外围类对象。然而,当内部类是static时,就不是这样了。嵌套意味着:
1)要创建嵌套类的对象,并不需要其外围类的对象。
2)不能从嵌套类的对象中访问非静态的外围类对象。
嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西。
10.7.1 接口内部的类
正常情况下,不能再接口内部防止任何代码,但嵌套类可以作为接口的一部分。你放到接口中的任何类都自动的是public和static的,因为类是static的,只是将嵌套类至于接口的命名空间内,这并不违反接口的原则。你甚至可以在内部类中实现其外围接口,就像如下:
public interface ClassInterface{ void howdy(); class Test implments ClassInterface{ public void howdy(){ System.out.println("Howdy!"); } public static void main(Stringp[] args){ new Test().howdy(); } } }
10.7.2 从多层嵌套类中访问外部类的成员
一个内部类被嵌套多少层并不重要——它能透明的访问它所嵌入的外围类的所有成员。
10.8 为什么需要内部类
一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入外围类的窗口。
内部类必须回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢?答案是:“如果这能满足需求,那么就这么做。”
那么内部类实现一个接口与外围类实现这个接口又有什么区别?答案是“后者不是总能享用到接口带来的方便,有时需要用到接口的实现。”所以使用内部类最吸引人的原因是:每个内部类都能独立的继承一个(接口的)实现,所以无论外围类是否已经继承了(接口的)实现,对于内部类都没有影响。
如果拥有的是抽象的类或具体的类,而不是接口,那只能使用内部类才能实现多重继承。
“覆盖”内部类就好像它是外围类的一个方法,其实不起什么作用。