《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 编译器并不是简单地为每一个引用都创建默认对象,这一点是很有意义的,因为若真要那样做的话,就会在许多情况下增加不必要的负担。如果想初始化这些引用,可以在代码中的下列位置进行:

  1. 在定义对象的地方。这意味着他们总是能够在构造器被调用之前被初始化。

  2. 在类的构造器中。

  3. 就在正要使用这些对象之前,这种方式被称为惰性初始化。在生成对象不值得及不必每次都生成对象的情况下,这种方式可以减少额外的负担。

  4. 使用实例初始化。


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类这种复用的形式就是组合,这个是通过包装和方法转发实现的。

接下来讲述组合优缺点

优点

  1. 组合不破坏封装,相对于继承

  2. 组合松耦合,包装类和被包装类彼此独立,不会因为被包装类突然加个方法就使得包装类多了一个方法,包装类视情况包装所需方法。

  3. 支持动态组合,组合的方式在运行时可以根据条件来选择所组合的类。

  4. 包装类可以通过包装改变被包装类的接口,比如被包装类是实现了Set接口的,我可以通过包装,让包装类实现Map接口。

缺点

  1. 不能实现多态

  2. 无法自动获得被包装类的接口,比如被包装类实现了Set接口,包装类并没有自动获得此接口,需要经过包装,才有可能和他一样的接口。

何时用继承,何时用组合

  这应该才是我们关心的问题吧。 在以下几种情况使用组合:

  1. 子类只需要继承父类的一部分,继承就没辙了。

  2. 如果只是为了具有父类的一些属性方法,比如汽车具有轮胎和发动引擎,但是如果为此继承这两个类是很不明智的,使用组合更为恰当。

  3. 如果设计的子类是为了复用代码,并不是为了扩展父类,那么最好是选组合的方式,因为父类改变会影响子类。对于只是为了复用而继承的类很不利。

  什么时候使用继承?

  1. 类之间很明显是一种is-a 关系,而不是has-a或者contain-a关系。

  2. 考虑多态时使用继承

另外:

组合优于继承是面向对象设计原则之一

 

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 为什么需要内部类

一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入外围类的窗口。

内部类必须回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢?答案是:“如果这能满足需求,那么就这么做。”

那么内部类实现一个接口与外围类实现这个接口又有什么区别?答案是“后者不是总能享用到接口带来的方便,有时需要用到接口的实现。”所以使用内部类最吸引人的原因是:每个内部类都能独立的继承一个(接口的)实现,所以无论外围类是否已经继承了(接口的)实现,对于内部类都没有影响。

如果拥有的是抽象的类或具体的类,而不是接口,那只能使用内部类才能实现多重继承。

10.10 内部类可以覆盖吗?

“覆盖”内部类就好像它是外围类的一个方法,其实不起什么作用。

 



posted @ 2020-11-26 23:52  Jesen等不等式  阅读(97)  评论(0编辑  收藏  举报