: 클래스나 함수에서 사용하는 자료형을 외부에서 지정할 수 있는 기능
: 함수나 클래스를 선언할 때 고정적인 자료형 대신 실제 자료형으로 대체되는 타입 파라미터를 받아 사용하는 방법

class A를 상속받은 class B가 있을 때
이 두 클래스의 인스턴스를 공용으로 사용하는 하나의 함수에 파라미터로 받으려고 한다.
앞서 말했듯 funTest(var a: A) 이런식으로 한다면 casting으로 A와 B 둘 다 사용하겠지만 이것은 단편적인 예시이고, 실제 코드상에서는 되게 많은 클래스들을 통한 인스턴스들의 객체들을 관리해야 한다면 어떻게 해야할지 고민이다.
이럴 때 사용하는 것이 제네릭인 것이다.
fun <T> genericFUnc(param: T): T
이런 식으로 타입에 대해 제네릭을 활용한다면 다양한 클래스의 인스턴스들을 하나의 함수에 공용으로 사용 가능할 것이다.
genericFunc(1) ->
fun <Int> genericFunc(var param: Int) {}
만약 함수호출 시 파라미터에 1을 넣는다면
함수에 대한 제네릭 타입은 추론으로 Int형으로 적용이 된다.
보통 제네릭 타입 명을 T라고 하는데, 이는 규칙은 아니다. 클래스 이름 지을 때 규칙과 동일하게 자유롭게 해도 되지만 관례적으로 Type의 T를 쓴다. 여러 제네릭을 병렬적으로 사용할 때는 T의 다음 알파벳인 T, U, V 식으로 사용한다.
<T: SuperClass>
특정한 수퍼클래스를 상속받은 클래스 타입으로만 제한하려면 T: SuperClass 라고 해야한다.

이미지를 보면 B와 C는 A로부터 상속받은 클래스로써 shout함수를 재정의했다.
UsingGeneric 클래스의 경우 수퍼클래스 A로 제한한 제네릭 T를 선언하고, 생성자에서는 T에 맞는 인스턴스를 속성 t로 받는다.
UsingGeneric 인스턴스를 만들 때 파라미터로 클래스 A의 인스턴스를 넘겨주면
A에 대한 shout함수 구문이 실행이된다.

A 뿐 아니라 B와 C를 넣어도 제네릭 T로 인해 자료형 추론하여 각 인스턴스가 들어가게 되고, 해당하는 재정의된 shout함수가 호출하게 된다.
이는 사실 직접 val t: A와 같이 수퍼클래스인 A로 캐스팅하여 함수를 호출해도 결과는 같을 수 있지만 제네릭 이용으로 자료형을 대체하게 되어 캐스팅 방지할 수 있기 때문에 성능을 더 높일 수 있다.

이렇게 함수에 대해 파라미터로 제네릭을 쓰게 된다면 어떠한 인스턴스를 파라미터로 보내도 타입 추론으로 반영하게 된다.