Total Pageviews

2017/11/07

Using MapStruct to simplify the implementation of mappings between Java bean types

Problem
Assume I have two java bean classes, Car and CarDto, I would like to copy Car attributes to CarDto attributes, and vice versa.

The Car class is as bellows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package albert.practice.mapStruct;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class Car {
    private String manufacturer;
    private int seatCount;
    private String mfgDateString;
}


The CarDto class is as following:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package albert.practice.mapStruct;

import java.util.Date;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class CarDto {
    private String make;
    private int numberOfSeats;
    private Date mfgDate;
}



The two Java bean classes have two differenet name and even have different data type, how to fulfill this requirement conveniently?




How-To
You can make good use of MapStruct to simplify the implementation of mappings between Java bean types.

Add the following configuration in pom.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    <properties>
        <org.mapstruct.version>1.1.0.Final</org.mapstruct.version>
    </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
            <version>${org.mapstruct.version}</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source> <!-- or higher, depending on your project -->
                    <target>1.8</target> <!-- or higher, depending on your project -->  
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>


Create a CarMapper to do column mapping
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package albert.practice.mapStruct.mapper;

import java.util.List;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

import albert.practice.mapStruct.Car;
import albert.practice.mapStruct.CarDto;

@Mapper(uses = MfgDateMapper.class)
public interface CarMapper {
    // By convention, a mapper interface should define a member called INSTANCE
    // which holds a single instance of the mapper type:
    CarMapper CarMapper = Mappers.getMapper(CarMapper.class);

    @Mapping(target = "mfgDate", source = "mfgDateString")
    @Mapping(target = "make", source = "manufacturer")
    @Mapping(target = "numberOfSeats", source = "seatCount")
    CarDto convertCarToCarDto(Car car);

    @Mapping(target = "mfgDateString", source = "mfgDate")
    @Mapping(target = "manufacturer", source = "make")
    @Mapping(target = "seatCount", source = "numberOfSeats")
    Car convertCarDtoToCar(CarDto carDto);
    
}




Create a MfgDateMapper class to convert date string to java.util.Date, and vice versa 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package albert.practice.mapStruct.mapper;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import com.google.common.base.Strings;

public class MfgDateMapper {

    private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

    public Date toDate(String dateString) throws ParseException {

        Date date = null;
        if (!Strings.isNullOrEmpty(dateString)) {
            try {
                date = dateFormat.parse(dateString);
            } catch (ParseException e) {
                throw e;
            }
        }
        return date;
    }

    public String toDateString(Date date) {
        String dateString = "";
        if (date != null) {
           dateString = dateFormat.format(date);
        }
        return dateString;
    }

}


Create a test class
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package albert.practice.mapStruct;

import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.mapstruct.factory.Mappers;

import albert.practice.mapStruct.mapper.CarMapper;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class CarMapperTest {

    CarMapper carMapper;

    @Before
    public void setup() {
        carMapper = Mappers.getMapper(CarMapper.class);
    }

    @Test
    public void testConvertCarToCarDto() {
        Car car = new Car("Toyota Camry", 5, "2017-04-01");
        CarDto carDto = carMapper.convertCarToCarDto(car);

        log.debug("[testConvertCarToCarDto] car = " + car.toString());
        log.debug("[testConvertCarToCarDto] carDto = " + carDto.toString());

        assertTrue(carDto.getMake().equals(car.getManufacturer()));
        assertTrue(carDto.getNumberOfSeats() == car.getSeatCount());
    }

    @Test
    public void testConvertCarDtoToCar() {
        CarDto carDto = new CarDto("MINI Cooper S", 4, new Date());
        Car car = carMapper.convertCarDtoToCar(carDto);

        log.debug("[testConvertCarDtoToCar] carDto = " + carDto.toString());
        log.debug("[testConvertCarDtoToCar] car = " + car.toString());

        assertTrue(car.getManufacturer().equals(carDto.getMake()));
        assertTrue(car.getSeatCount() == carDto.getNumberOfSeats());
    }

}






2017/11/06

[Angular] How to implement pagination in ng-grid

Problem
How to implement pagination in ng-grid?

How-to

Here has code snippet:

1. enable pagination function, and set pagingOptions & totalServerItems in ng-grid

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    $scope.gridCols  = [ 
        {  field : 'no',              displayName : '序號',         width : '5%',   cellClass : 'text-right' }, 
        {  field : 'exceptionGroup',  displayName : '異常類別',     width : '15%',  cellClass : 'text-left' }, 
        {  field : 'exceptionDesc',   displayName : '異常類別說明', width : '25%',  cellClass : 'text-left' }, 
        {  field : 'deptName',        displayName : '部門別',       width : '15%',  cellClass : 'text-left' }, 
        {  field : 'userName',        displayName : '通報人員姓名',  width : '10%',  cellClass : 'text-left' }, 
        {  field : 'emailAddr',       displayName : '收件Email',    width : '30%',  cellClass : 'text-left' } 
    ];
        
    $scope.dataGrid = {
        multiSelect : false,
        data : 'itemData',
        columnDefs : 'gridCols',
        enableColumnResize : true,
        keepLastSelected : false,
        enablePaging : true,
        showFooter : true,
        pagingOptions : $scope.pagingOptions,
        totalServerItems : "totalServerItems"
    };


2. set initial values when page loaded
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    // query result (current page)
    $scope.itemData = [];
    // query result (complete data)
    $scope.resultData = [];     
        
    // set page sizes, page size and current page
    $scope.pagingOptions = {
        pageSizes : [ 25, 50, 100 ],
        pageSize : 25,
        currentPage : 1
    };
    // total items are on the server (initial value)
    $scope.totalServerItems = 0;        

3. set data to current page
1
2
3
4
5
6
7
8
9
    // set paging data
    $scope.setPagingData = function(data, page, pageSize) {
        var pagedData = data.slice((page - 1) * pageSize, page * pageSize);
        $scope.itemData = pagedData;
        $scope.totalServerItems = data.length;
        if (!$scope.$$phase) {
            $scope.$apply();
        }
    };


4. Registers a listener callback to be executed whenever the pagingOptions changes.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    // Registers a listener callback to be executed whenever the pagingOptions changes.
    $scope.$watch('pagingOptions', function(newVal, oldVal) {
        // if page size change, then set current page to 1 and set paging data
        if(newVal.pageSize !== oldVal.pageSize) {
            $scope.pagingOptions.currentPage = 1;
            $scope.setPagingData($scope.resultData, $scope.pagingOptions.currentPage, newVal.pageSize);
        }
   
        // if current page change, then set paging data
        if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage) {
            $scope.setPagingData($scope.resultData, newVal.currentPage, newVal.pageSize);
        }
    }, true);     


Screenshot is as bellows:



Reference

[1] https://angular-ui.github.io/ui-grid/

2017/11/05

[Java] Make a unique list of objects

Problem
I am using do query to retrieve a List of Cam101wGridData data. The data contains user name and email address. 
User asks to remove duplicate email address from the data list, how to do it?
1
    List<Cam101wGridData> result= doQuery(form, result);


How-To
Steps are as following:
Step1. Create a Cam101wEmailCsvBean and override equals and hashCode methods (using email attribute)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    package x.x.x.model;
    
    import java.io.Serializable;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.EqualsAndHashCode;
    import lombok.NoArgsConstructor;
    
    /**
     * Mail格式下載 bean (解決 Email 重複的資料)
     *
     * Any class definition may be annotated with @EqualsAndHashCode to let lombok generate
     * implementations of the equals(Object other) and hashCode() methods. By default, it'll use all
     * non-static, non-transient fields, but you can exclude more fields by naming them in the optional
     * exclude parameter to the annotation. Alternatively, you can specify exactly which fields you wish
     * to be used by naming them in the of parameter.
     */
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    @EqualsAndHashCode(of = "email")
    public class Cam101wEmailCsvBean implements Serializable {
        private static final long serialVersionUID = 1L;
    
        /**
         * 會員姓名
         */
        private String userName;
    
        /**
         * E-Mail
         */
        private String email;
    
    }


Step2. Convert from List<Cam101wGridData>  to List<Cam101wEmailCsvBean>:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
   /**
     * 將 List<Cam101wGridData> 轉成 List<Cam101wEmailCsvBean>.
     * 
     * @param result List<Cam101wGridData>
     * @return List<Cam101wEmailCsvBean>
     */
    private List<Cam101wEmailCsvBean> convertToCam101wEmailCsvBean(List<Cam101wGridData> result) {
        List<Cam101wEmailCsvBean> emailCsvBeans = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(result)) {
            for (Cam101wGridData row : result) {
                Cam101wEmailCsvBean emailBean = new Cam101wEmailCsvBean(row.getUserName(),
                        row.getEmail());
                emailCsvBeans.add(emailBean);
            }
        }
        return emailCsvBeans;
    }


Step3. Convert List to Set (remove duplicate email), and convert Set to List 
1
2
3
4
5
6
7
8
   private List<Cam101wEmailCsvBean> getDistinctEmailCsvBean(List<Cam101wEmailCsvBean> emailCsvBeans) {
       List<Cam101wEmailCsvBean> distinctEmailCsvBeans = new ArrayList<>();
       
       Set<Cam101wEmailCsvBean> emailCsvSet = new HashSet<>(emailCsvBeans);
       distinctEmailCsvBeans.addAll(emailCsvSet);
       
       return distinctEmailCsvBeans;
   }


Code snippet (Before and after)
1
2
3
4
5
6
7
    // before (with duplicate emails)
    List<Cam101wGridData> result= doQuery(form, result);
    
    // after (with distinct email)
    List<Cam101wGridData> result= doQuery(form, result);
    List<Cam101wEmailCsvBean> emailCsvBeans = convertToCam101wEmailCsvBean(result);
    List<Cam101wEmailCsvBean> distinctEmailCsvBeans = getDistinctEmailCsvBean(emailCsvBeans);




Reference
[1] https://projectlombok.org/features/EqualsAndHashCode.html    
[2] https://stackoverflow.com/questions/20126470/java-how-to-keep-unique-values-in-a-list-of-objects




2017/11/04

[Eclipse] How to know current worksapce ?

Problem
How do I know which eclipse workspace I am working currently?

How-To

File->Switch Workspace->Other... and it will show the name of current workspace.


2017/11/03

SMS Service with Failsafe Mechanism

Scenario


How-To
Making good use of failsafe library can let you handle failure easily.
Remember to add dependency into your pom.xml
1
2
3
4
5
6
        <!-- retry -->
        <dependency>
            <groupId>net.jodah</groupId>
            <artifactId>failsafe</artifactId>
            <version>1.0.4</version>
        </dependency>


Sample code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
    private void sendSmsWithRetry(final String otp, final SmsParams params) {
        final String mobilePhone = params.getMobilePhone();
        final CallerEnum sourceName = params.getSourceName();
        final String policyNumber = params.getPolicyNumber();
        final String userId = params.getUserId();

        RetryPolicy retryPolicy = new RetryPolicy();
        retryPolicy.withDelay(1, TimeUnit.SECONDS); // Sets the delay between retries.
        retryPolicy.withMaxRetries(5);              // Sets the max number of retries to perform. -1 indicates to retry forever.

        // Creates and returns a new SyncFailsafe instance that will perform executions and 
        // retries synchronously according to the retryPolicy.
        Failsafe.with(retryPolicy)
        // Registers the listener to be called when an execution is successful.
        .onSuccess(new CheckedConsumer() {
            @Override
            public void accept(Object t) throws Exception {
                // The execution is successful, then write success log into database
            }
        })
        // Registers the listener to be called when an execution attempt fails.
        .onFailedAttempt(new CheckedConsumer() {
            @Override
            public void accept(Object t) throws Exception {
                if (t instanceof Exception) {
                    Exception e = (Exception) t;
                    // The execution attempt fails, then write error log into database
                }
            }
        })
        // Registers the listener to be called when an execution fails and cannot be retried.
        .onFailure(new ContextualResultListener() {
            @Override
            public void onResult(Object result, Throwable failure, ExecutionContext context)
                    throws Exception {
                String exeMsg = "驗證碼傳送失敗。手機號碼:" + mobilePhone + ", 失敗原因:" + failure.getMessage();
                failure.printStackTrace();                
                throw new RuntimeException(exeMsg);
            }
        })
        // Executes the runnable until successful or until the configured RetryPolicy is exceeded.
        .run(new CheckedRunnable() {
            @Override
            public void run() throws Exception {
                sendSms(mobilePhone, getSmsText(params.getOtpServiceType(), otp));
            }
        });
    }

2017/11/02

[Java] MessageFormat

Problem
How to takes a set of objects, formats them, then inserts the formatted strings into the pattern at the appropriate places.

Example 1.



Example 2.



How-To

Here has an example to demonstrate how to do message format:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
package albert.practice.stringFormat;

import java.text.Format;
import java.text.MessageFormat;

public class StringFormatExample {
    
    public String getFormattedMessage(String message, String... values) {
       Format messageFormat = new MessageFormat(message);
       return messageFormat.format(values);
    }
    
}


Test case is as bellows:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package albert.practice.stringFormat;

import static org.junit.Assert.assertEquals;

import org.junit.Before;
import org.junit.Test;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class StringFormatExampleTest {

    private StringFormatExample stringFormat;
    private String unformatedMessage = "";
    private String phone = "";
    private String otp;
    private String anotherUnformatedMessage;
    private String error;
    
    @Before
    public void setup() {
        stringFormat = new StringFormatExample();
        unformatedMessage = "驗證碼傳送成功。手機號碼: {0}, 簡訊內容:驗證碼為:{1}";
        anotherUnformatedMessage = "驗證碼傳送失敗,失敗原因:{1}。手機號碼: {0}";
        phone = "0910123456";
        otp = "12345";
        error = "HTTP Server is down";
    }
    
    @Test
    public void testReturnWith1000Separator() {
        String str = stringFormat.returnWith1000Separator(1234567890);
        log.debug(str);
        assertEquals("1,234,567,890", str);
    }
    
    @Test
    public void testGetFormattedMessage() {
        String formatedMsg = stringFormat.getFormattedMessage(unformatedMessage, phone, otp);
        log.debug("formatedMsg = " + formatedMsg);
        assertEquals("驗證碼傳送成功。手機號碼: 0910123456, 簡訊內容:驗證碼為:12345", formatedMsg);
        
        String anotherFormatedMsg = stringFormat.getFormattedMessage(anotherUnformatedMessage, phone, error);
        log.debug("anotherFormatedMsg = " + anotherFormatedMsg);
        assertEquals("驗證碼傳送失敗,失敗原因:HTTP Server is down。手機號碼: 0910123456", anotherFormatedMsg);
    }

}
 

print logs:
1
2
14:57:18.679 [main] DEBUG a.p.s.s - formatedMsg = 驗證碼傳送成功。手機號碼: 0910123456, 簡訊內容:驗證碼為:12345
14:57:18.679 [main] DEBUG a.p.s.s - anotherFormatedMsg = 驗證碼傳送失敗,失敗原因:HTTP Server is down。手機號碼: 0910123456 



2017/11/01

[閱讀筆記] Common Stocks and Uncommon Profits (3/4)


  1. 既然是保守投資者,又要挑選出成長股,這兩者邏輯是否牴觸?其實不然,當考量到通膨的影響時,你就會理解成長型的公司的重要性。面對腳步越來越快的世界,公司不可能站在原地不動,一家公司不是成長就是萎縮,強大的攻擊力就是最好的防禦力。一家無法往上走的公司,無可避免地只會往下走下坡
  2. 保守/謹慎投資的第二個象限:第一象限強調的是公司對於生產、行銷、研發與財務的能力,第二象限強調是公司在生產、行銷、研發與財務部門是否有稱職的人選,朝向公司的目標前進
  3. 若一家大公司,需要從公司外部找人來擔任該公司的CEO,而不是從內部拔擢,這肯定是個壞兆頭。無論現在的財報多漂亮,其代表公司內部出了一些問題
  4. 若一家公司的薪資結構是,排名第一名的比第二名、第三名的人多非常多,這就是一個警訊。代表其薪資差異是劇烈的往下掉,而非緩和下降,這也同時告訴你,主要的錢都給了最高主管,底下的員工沒有得到該有的報酬
  5. 一家公司是否適合保守投資與長期持有的三個條件
    1. 公司必須體認到這個世界的運作不斷在變化,且速度越來越快:若只沉浸在過去成功的紀錄,以為過去的作法在未來依樣有效,這家公司只會走下坡
    2. 公司必須善待每個階層的員工:包含創造良好與氣氛融洽工作環境、正面的處理員工的抱怨、讓優秀員工得到財務上的獎勵與公司的認可等。將員工視為重要資產的公司,對於長期投資的投資人來說,是非常重要的因素
    3. 有遠見的公司,不會急著在每個會計期間結束時,產生最漂亮的盈餘數據,真正的成長導向的公司不會這麼做:成長導向的公司會聚焦在賺的錢要足夠能投入研發企業的新產品、新流程。今天花在研發的每一塊錢,在未來可能會賺到好幾塊錢
  6. 真實的投資成長目標不僅僅只是賺錢,而是避免損失
  7. 保守/謹慎投資的第三個象限:
    1. 公司有沒有它可以做,但是別人做得沒有他好的產品或服務?
    2. 其是否能夠長期維持高於業界平均的獲利能力?
  8. 高於業界平均的獲利能力,對於投資者非常重要。但是該如何維持高於平均的獲利能力?擁有專利保護是一種方式,另外一種是讓現有或潛在對手沒有任何誘因,不想進入此領域,也是一種方式
  9. 所謂的高於業界平均的利潤,指的是大約比第二名的競爭者高2~3%左右,會是一個好的狀況,當其利潤高過其他競爭者太多,反而會引誘其他競爭者進入
  10. 若公司要維持長久的高獲利,期必須要滿足兩個要件:
    1. 公司所銷售的產品或服務必須建立起高品質與可靠度的聲譽
    2. 公司所銷售的產品或服務對象,要是眾多的小客戶,而非少數的大客戶
  11. 當泡沫出現時,你無法預估他會持續多久後才破滅,有可能持續幾年,也有可能只出現幾個月
  12. 對於一個保守謹慎的投資人,若投資標的符合前三個象限,且本益比也很低的話,這是一個最安全的標的
  13. 本益比 (price-earnings ratio) 並不是從真實發生的事情所產生的,其是從金融圈 (financial community) 目前所深信的事情所產生。不要單靠本益比來作為買賣的決定因素,因為本益比並非客觀的數據
  14. 利率是影響股市的最重要的因素,若利率走高,資金會從股市匯出,導致股市走低;若利入走低,資金會流入股市,推升股價
  15. 一個好的企業需具備兩個基本的特性
    1. 有能力規劃長期的計畫,而不是總是著眼於每日例行事務上的績效提升
    2. 當犯下重大錯誤時,必須清楚認清所犯下的錯誤,並採取補救措施,記取教訓
  16. 一家值得投資的公司,其不只要擁有強大的銷售團隊來銷售公司產品,還要能密切觀察持續變動的需求,以及客戶想要什麼
  17. 聰明與愚笨的主要差別在,聰明的人會從錯誤中學習到教訓,愚笨的人則是一直重複犯錯
  18. 公司由上到下的人員素質很重要,尤其這兩項
    1. 企業能力 (business ability):此能力包含基層員工每日例行執行事項是否有優於業界的工作效率與品質,高階主管是否有規劃長遠的計畫能力,維持長期成長動能
    2. 經營者的是否正派 / 正直 (decency):不正派 / 正直的經營者,只會想到自己的利益,不會理會股東們的利益,也不會擁有熱情且有忠誠度的工作團隊
  19. Shakespeare曾說過:人生潮起潮落,若能把握機會乘風破浪,必能馬到成功。若不能把握機會,人生的航行就只能受限於悲苦之中。我們漂流在茫茫大海中,當浪潮來時,我們必須把握住機會;若否,則會使我們的冒險失敗。投資不嘗試如此嗎?
  20. 當根據前述的原則挑選的股票,請至少持有三年,三年期滿再根據其績效來決定是要繼續持有還是賣出
  21. 如果發現一檔好的且價格合理的股票,不要為了要以10元、10.25元或10.5元買進而躊躇 (battling for “eighths and qiarters”)。當你沒有買到,在股票一飛衝天之後,你才會發現你犯了昂貴的錯誤
  22. 不要晉升一個不曾犯過錯誤的人,因為這種人從來沒做過什麼事情
  23. 如果你無法做得比別人好,不如不要做
  24. 不要去投資你不熟悉的產業,你要知道你能力的限制
  25. 如果一家公司的未來前景是正面的,即便已知在不久的將來會從目前高點下跌,我會建議繼續持有而非賣出。一家好的公司,會再爬起來並超越過去的高點
  26. 如果投資的標的是一個財務穩健的且管理良好的公司,即便是遇到猛烈的熊市,也不會抹除其持有的價值。我的建議是,請忽略短期的波動
  27. 短期的價格波動是非常難以預測的,我不相信頻繁的殺進殺出的遊戲 (in and out game) 可以讓你賺到可觀的利潤