Overloading Constructor with Generic Class

Mình có khải báo một generic class như sau:

class CardboardContainer<T> {
    T myField;

    CardboardContainer(T myField) {
        System.out.println("In T constructor");
        this.myField = myField;
    }

    <T extends Number> CardboardContainer(T myField) {
        System.out.println("In T2 constructor");
//        this.myField = myField;
    }
}

Với các thử nghiệm sau:

CardboardContainer<String> n1 = new CardboardContainer<String>("Hello"); // In T constructor

CardboardContainer<String> n2 = new CardboardContainer<String>(3); // In T2 constructor

CardboardContainer<Integer> n22 = new CardboardContainer<Integer>(3); // In T constructor

CardboardContainer<String> n3 = new <Integer>CardboardContainer<String>(3); // In T2 constructor

Tuy nhiên mình chưa giải thích được kết quả, dựa vào đâu mà nó quyết định chọn 1 trong 2 constructor.

Mọi người giúp mình với.

1 Like

Đầu tiên bạn nên đặt tên 2 Generic khác nhau để tránh nhầm lẫn.
Giả sử T1 cho class và T2 cho constructor T2.
1.

CardboardContainer<String> n1 = new CardboardContainer<String>("Hello");

Trường hợp trên chắc chắn sẽ rơi vào constructor T1, vì đối số là String, không extends từ Number.

2.

CardboardContainer<String> n2 = new CardboardContainer<String>(3);

Trường hợp tiếp theo chọn T2 constructor, vì T1 là String, và T2 là Number

3.

CardboardContainer<Integer> n22 = new CardboardContainer<Integer>(3);

Ở đây thì khó hiểu hơn, vì dường như nó khớp với cả constructor T1 và T2.
Vì T1 là Integer rồi, nên khi này 2 constructor có thể viết lại là

<Integer> CardboardContainer(Integer myField) {
   System.out.println("In T1 constructor");
}

<T extends Number> CardboardContainer(T myField) {
   System.out.println("In T2 constructor");
}

Có thể thấy là T1 chuyên biệt hơn (Integer là lớp con của Number), nên T1 constructor sẽ được chọn.

4.

CardboardContainer<String> n3 = new <Integer>CardboardContainer<String>(3);

Trường hợp này tương tự trường hợp thứ 2


Như vậy, không nên áp dụng code khó hiểu thế này trong thực tế, giảm tính đọc hiểu và tiềm tàng lỗi. Ví dụ khi chạy trường hợp sau, T1 và T2 sẽ được suy ra giống hệt nhau (Number):

CardboardContainer<Number> n22 = new CardboardContainer<Number>(3);

3 Likes

Không đúng! Trường hợp 2 vẫn là một chuỗi như, ví dụ: “”+3 giống như “3”. Trường hợp 3 đúng là Số nguyên 3

1 Like

Với trường hợp 1:

Mình đã thử trường hợp này:

CardboardContainer<Integer> n11 = new <Integer>CardboardContainer<Integer>(1);

Nếu theo lý giải của bạn thì nó sẽ rơi vào hàm thứ 2, chứ không phải hàm thứ nhất. Nhưng thực tế nó rơi vào hàm thứ nhất.

Với trường hợp thứ 2:

Cũng giống trường hợp 1, mình đã thử;

CardboardContainer<Integer> n22 = new CardboardContainer<Integer>(3);

vẫn gọi constructor thứ nhất mà theo cách bạn nghĩ thì nó phải gọi hàm thứ 2 vì type argument là subtype của Number

Với trường hợp 3:

Mình không nghĩ 2 cái này tương đương với nhau:

    CardboardContainer(T myField) {
        System.out.println("In T constructor");
        this.myField = myField;
    }

<Integer> CardboardContainer(Integer myField) {
   System.out.println("In T1 constructor");
}

Theo mình hàm đầu là non-generic constructor và ham 2 là generic constructor, bản chất khác hoàn toàn nhau, mình chưa thấy có một sự biến đổi tương đương nào giữa 2 hình thức này.

Mình có lý giải là

nó chọn hàm T1 là hàm thứ nhất chứ nhỉ, sao bạn lại bảo mình nói ngược lại ?
Bạn đọc kỹ lại đi.

Khi đối số là String thì nó chỉ có T1 đáp ứng, nhưng khi đối số là Integer thì T1 và T2 đều đáp ứng, nên cần xét thêm xem T1 hay T2 chuyên biệt hơn (gần tham số hơn trong nhánh cây kế thừa)

x là biến, còn 3 là giá trị.
Nhưng khi x = 3, thì tại thời điểm sử dụng, x không khác gì số 3.

Bạn đọc kỹ hơn đi.
Mình viết ra để diễn tả sự tương đương, rằng khi này T đã được coi là Integer, kiểu dữ liệu số nguyên có sẵn của Java:

<Integer> CardboardContainer(Integer myField)
1 Like

You don’t have to try that :grinning:. The general term T stands for “type parameter”. This means that the actual parameter type determines the type of the outcome.

CardboardContainer< String > n2 = new CardboardContainer< String >(3);

T is String hence the outcome of 3 will be converted to String (""+3)

CardboardContainer< Integer > n22 = new CardboardContainer< Integer >(3);

T is Integer and the outcome is a real integer 3. Similar to T as Float, Double, etc.

1 Like

Hi, mình đi làm cả ngày, đến cuối ngày mới về nhà check được nên reply muộn, bạn thông cảm ha.

Cái phần bạn nói thêm, thì bạn viết ở 3. nên mình không đưa vào context của phần 1. để mình tóm tắt lại.

Trường hợp 1: bạn giải thích

thì mình đưa ra một ví dụ khác:

Lúc này: theo lý luận của bạn thì mình đã đổi type argument từ String thành Integer để thoả mãn điều bạn nói cho nó extend từ Number như vậy nó sẽ vào constructor thứ 2.

Nhưng thực tế là không vào hàm thứ 2 mà nó vào hàm thứ nhất cho dù bạn type argument là String hay Integer. Hay ý bạn là thế nào

I completely agree with you abou this:

Thanks for your useful information. I will check this again. Howewer, I want to understand clearly, There are still many things I don’t understand, and the above are just a few examples.

You know Java evolved from C and is still being perfected. One of the perfections is the GENERIC. There are 5 important generic terms:

  • T for Type and is used for generic NON-primitive types like String, Integer, Double etc.
  • E for Element and is used for generic elements used by collections frameworks like in Set< E >, Queue< E > etc.
  • K for Key and is used for generic keys like in Map< K, V > where K can be String, int, Integer etc.
  • V for Value and is used for generic values ​​as RETURN value or in conjunction with K where V can be any object (primitive, non-primitive etc.)
  • N for Number and is used for numeric values.

The generic wildcard character is ? (example: Class< ? > or List< ? > etc.). More: Click HERE

Mình đã nói ở trên rồi mà nhỉ.

Ở trường hợp đầu nó thỏa mãn chỉ 1 constructor, nên nó sẽ chọn constructor này.

Khi bạn truyền Integer vào, thì nó thỏa mãn 2 constructor, về kiểu dữ liệu, nên cần xem xét thêm về tính chuyên biệt.

Nếu bạn thử với

CardboardContainer<Number> n22 = new CardboardContainer<Number>(3);

Thì code sẽ lỗi, vì khi này tham số 2 constructor T1 và T2 sẽ giống hệt nhau.

Thì mình đang giải thích 1 với context của 1, còn bạn nêu 1 ví dụ giống trường hợp 3 thì bạn phải lấy lời giải thích ở 3 chứ nhỉ. Bạn lại đi lấy 1 ví dụ giống 3, rồi lấy giải thích ở 1 của mình để áp vào là không hợp lý.

Và bạn suy diễn sai ở câu sau:
“Trường hợp trên chắc chắn sẽ rơi vào constructor T1, vì đối số là String, không extends từ Number.”

Mình nói đối số là String, không phải Number nên nó sẽ rơi vào T1, chứ không có kết luận nếu là Number thì nó sẽ rơi vào T2, mà khi này, cần phải xét thêm.

1 Like

Thứ nhất:

Bạn cho rằng 2 cái này là giống nhau, cho rằng mình lấy 1 ví dụ giống 3, trong khi bạn chưa chứng mình gì cả.

  • trường hợp 3
CardboardContainer<Integer> n22 = new CardboardContainer<Integer>(3);
  • ví dụ bổ sung với trường hợp 1 của mình
CardboardContainer<Integer> n11 = new <Integer>CardboardContainer<Integer>(1);

Thứ hai:

Mình không suy diễn, mình chỉ đang thấy phần giải thích của bạn chưa đầy đủ, dẫn đến có nhiều vấn đề, việc mình nghĩ như vậy cũng xuất phát từ kết luận của bạn mà.

Kết luận tổng quát của bạn là gì?, xét thêm những cái gì?. Mình nghĩ nên tập trung vào trường hợp 1 trước.

Bảo mọi người giúp mà khi có người giúp lại thay vì đọc hiểu xem người ta muốn nói gì, bạn lại cứ dùng kiến thức của mình để phán xét à?

Như @Duc_Manh_Pham đã nói ở post #2, nó sẽ dựa vào cái nào match specific hơn thì nó chọn.

Viết lại code của bạn cho dễ hiểu:

class CardboardContainer<T> {
    CardboardContainer(T myField) {
        System.out.println("In T constructor");
    }

    <T2 extends Number> CardboardContainer(T2 myField) {
        System.out.println("In T2 constructor");
    }
}

Khi cấp phát thì sẽ là new <T2>CardboardContainer<T>(U); với U là kiểu của tham số, và T2 là optional.

Bây giờ có 2 trường hợp:

  • Nếu U != T => T constructor không match, chỉ có thể xét T2 constructor, trường hợp n2n3
  • Nếu U == T => giữa specific T constructor và generic T2 constructor thì nó sẽ chọn cái specific hơn (là T constructor), trường hợp n1, n22 và mới nhất là n11
3 Likes

Mình thì không biết về Java và cũng tham gia xem, liệu chủ topic có thông không chứ có vẻ topic này dạng đang kiểu như A: “tại sao con ngỗng kêu to?”. B: vì con ngỗng có cổ dài? A: con ễnh ương có cổ dài không mà vẫn kêu to? B: à, vì nó có bình hơi ở cổ. Blah blah kiểu này thì tất cả chúng ta cần phải học lại môn “Nhập môn logic học” để tránh vi phạm nguyên tắc suy luận, cụ thể là tam đoạn luận. Có bạn Đức Mạnh gì đó đang giải thích 1, Nobita không chịu nghe, đi thay đổi để thành 3 rồi bật lại 1, thành ra ông nói gà bàn nói vịt, vi phạm nguyên tắc về logic học.

Thử xét từng cái xem:
1.

CardboardContainer<String> n1 = new CardboardContainer<String>("Hello"); // In T constructor

Đoạn trên không có gì phải giải thích, quá dễ hiểu, vì String từ đầu tới cuối.


CardboardContainer<String> n2 = new CardboardContainer<String>(3); // In T2 constructor

Đoạn trên vì sao ra “In T2 constructor” theo mình hiểu như này: n2 ta khai báo là String, xong truyền vào một Integer. Vậy thì nó xử lý sao? Nó sẽ tìm xem bên trong có cái nào là String không để rẽ qua đó. Không có cái nào String cả. Vậy giờ sao? Vậy thì “nó” (nào đó mình không rõ, chủ topic dùng nó thì mình cũng dùng nó, trong khi lẽ ra là him/ hoặc I) xem thằng nào gần gần nhất với Integer là 3 (mình không rõ Java gị là Number hay Integer hay có cả 2 nên dùng là “số nguyên” đi nhé), 3 (là số nguyên, phân biệt với “3”). Nó xét thấy "à, thằng 3 này dường như là số, có cái nào là số không thì ưu tiên. Ô là la, T extends Number là gần nhất, quăng cho nó cái bomb.


CardboardContainer<Integer> n22 = new CardboardContainer<Integer>(3); // In T constructor

Thằng này thì sao? Khai báo n22 là Integer, truyền vào cho nó là 3, số và số nốt, chẳng có cái gì phải lăn tăn cả, duyệt từ trên xuống, thằng nào có tham số là số thì dùng. Khỉ thật, nó đứng ngay đầu hàng, chuyển quả cho nó đi dù nó đang là “thằng cô dâu” chứ không phải đám gái xinh bưng quả. Vậy là In T constructor (nhà gái nhận quả nào).


CardboardContainer<String> n3 = new <Integer>CardboardContainer<String>(3);

Của quý gì đây? Khai báo một thằng n3 là String. Param của nó cũng Sờ tring luôn, nhưng điên rồ thật, sao lại có trò ép kiểu Integer và truyền vào nó là một số thật, nhưng lại có tham số lại là String, truyền vào đối số là 3. Rối rắm, điên rồ. Xem cái ký tự/ chuỗi ký tự kia có phải là ký tự chữ số hay không? Nếu phải thì ép kiểu cho nó thành Integer, và kiếm thằng Constructor nào liên quan đến số thì quăng cho nó. Úi chà, tìm thấy có thằng T extends Number, quăng cho nó đi. Nếu không ép được kiểu thì nghỉ chơi, báo lỗi (cái này mình tự nghĩ ra, không biết thực tế thế nào nếu thay vì 3 mà thành n thì ra sao).

Sau khi mình đọc tới đọc lui thì có vẻ ông Nobita1 hổng kiến thức, ổng chưa rành phía sau cú pháp thực sự là cái gì, ảnh hưởng/ chịu ảnh hưởng của gì khác. Có lẽ cần tìm hiểu lại Java các phiên bản cũ hơn hoặc tìm trong OOP xem bản chất đang là cái g? Quen hay lạ? Có hiểu nó chưa? Có cái tên nghe có vẻ rối rắm, cú pháp lạ nhưng bản chất của nó chính là cái “xưa như quả đất”, đã biết từ trước trong “trang X, chương Y của sách Flip flop OOP”.

Ví dụ như mình dùng chữ “ép kiểu” ở trên có thể nghe tào lao, và tự suy luận, nhưng theo mình bản chất có lẽ là vậy. Và rõ ràng là 1 coder không nên sa đà vào những cái rối rắm quá sớm, nên viết mã theo cách dễ hiểu với bản thân. Đến một lúc nào đó khi lập trình đủ nhiều, hoặc đọc được code người khác phát hiện ra hay ho, kỹ năng sẽ tăng lên, rồi vọc mấy cái rối rắm sau. Ví dụ luôn, JavaScript hiện đại cứ toàn mấy cái hàm => nhưng mình chưa thông nên chưa dùng, rồi forEach nữa, mình cứ lặp truyền thống. Chừng nào các xưa không được nữa thì tính.

1 Like

3 posts were merged into an existing topic: Topic lưu trữ các post off-topic - version 3

@Nobita1 bạn lưu ý không công kích cá nhân các thành viên khác nhé.

Cái ví dụ mình đưa ra, mình không có nói là nó giống 3, đấy là quan điểm của bạn đó. Bạn có thể chú ý đến sau new nó có thêm <Integer>, mình cũng muốn làm rõ việc có hay không có Integer ở vị trí này ảnh hưởng đến chọn constructor thế nào. Cùng như nếu có quan điểm giống 3 thì dựa trên cái gì.

Ý mình là chuyện con ễnh ương với con ngỗng ví dụ ấy. Bạn cứ lái đi đâu vậy.

Có nghĩa là bạn đó đang bàn dòng này:


Đây là con ngỗng.

Bên trên là ý bạn @Duc_Manh_Pham và mình hiểu, không lăn tăn gì cả.

Nhưng, trong khi đó bạn @Nobita1 lại không lăn tăn về đang bàn là bạn thấy thế nào (đúng/ sai/ hay/ dở/ hiểu/ chưa hiểu/ vướng mắc???). Mà lại dẫn cái sau vào:


Đây là con ễnh ương

Thì theo quan sát của người ngoài, mình và các bạn khác thì 2 bạn đang 1 người nói ngỗng, người kia nói ếnh ương, vi phạm nguyên tắc lập luận.

Vậy thì @Nobita1 giờ đây nếu cần thảo luận trường hợp 2 của bạn (lăn tăn điểm nào cái này, không có dính gì cái 1 kia nữa, vì nếu dính, sao cái 1 bạn không lăn tăn?) thì đặt câu hỏi, thắc mắc cần được giải đáp từ @Duc_Manh_Pham

Chuyện @Nobita1 bỗng nhiên thay đổi hoàn toàn từ:

CardboardContainer<String> n1 = new CardboardContainer<String>("Hello"); // In T constructor

thành:

CardboardContainer<Integer> n11 = new <Integer>CardboardContainer<Integer>(1);

thì gọi là “bố ai hiểu được”, nói thật là như vậy để rồi bật vào cái người ta đang giải thích về cái có chữ Hello kia, lập luận quá kỳ cục, thực sự là vậy.

Xin nói rõ thêm, cái này là dựa trên lời bạn @Duc_Manh_Pham :"

Bạn lại đi lấy 1 ví dụ giống 3

Còn lời của mình là đã ví dụ trong con ngỗng và con ễnh ương. Và bạn thắc mắc cái gì thì ghi rõ, chứ không phải là lâu lâu thòi ra cái nọ cái kia mà trước đó bạn không hỏi, sao ai biết bạn đang mong đợi gì? Bạn nghĩ người khác tự nghĩ ra được trong đầu giống như cái trong đầu bạn hay sao?

Giờ thì tập trung vào vấn đề đi, cái nào bạn thấy lấn cấn vì do đâu thì hỏi cho rõ hoặc bạn vẫn không hiểu thì xoáy sâu vào.

Ví dụ như, nếu là mình ở truòng hợp bạn, như này:

CardboardContainer<String> n3 = new <Integer>CardboardContainer<String>(3); // In T2 constructor

Mình sẽ diễn giải cho mọi người “tui hiểu đoạn code trên như vầy có đúng hông? Tui khai báo một biến/ object n3 có kiểu là Class, class này cho phép tham số để tuỳ biến, nhằm chọn Constructor phù hợp. Tui chưa tìm thấy tài liệu lý giải chỗ này vì sao String mà tui nạp số vô rồi nó nhảy vào Constructor 2, trong khi đó tui nghĩ lẽ ra nó phải nhảy vào Constructor 1. Vậy, ai đó giải thích cho tui kiên nhẫn chi tiết hông? Hoặc cho tui xin tài liệu giải thích. Tui rất là cám ơn và mời một bữa cà phê nếu có dịp gặp nhau ngoài đời”.

@noname00 bạn chỉ giúp mình đoạn mình công kích cá nhân.

3 posts were merged into an existing topic: Topic lưu trữ các post off-topic - version 3

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?