పరిచయం
JSE 5.0తో ప్రారంభించి, జావా భాష యొక్క ఆర్సెనల్కు జెనరిక్స్ జోడించబడ్డాయి.
జావాలో జెనరిక్స్ అంటే ఏమిటి?
జెనరిక్లు అనేది జెనరిక్ ప్రోగ్రామింగ్ను అమలు చేయడానికి జావా యొక్క ప్రత్యేక మెకానిజం — డేటా మరియు అల్గారిథమ్లను వివరించే మార్గం, ఇది అల్గారిథమ్ల వివరణను మార్చకుండా విభిన్న డేటాటైప్లతో పని చేయడానికి మిమ్మల్ని అనుమతిస్తుంది. ఒరాకిల్ వెబ్సైట్ జెనరిక్స్కు అంకితమైన ప్రత్యేక ట్యుటోరియల్ని కలిగి ఉంది: "
పాఠం ". జెనెరిక్లను అర్థం చేసుకోవడానికి, అవి ఎందుకు అవసరమో మరియు అవి ఏమి ఇస్తాయో మీరు ముందుగా గుర్తించాలి. ట్యుటోరియల్లోని "
ఎందుకు జనరిక్స్ని ఉపయోగించాలి? " అనే విభాగం కంపైల్ సమయంలో ఒక జంట ప్రయోజనాలను బలమైన టైప్ చెక్ చేయడం మరియు స్పష్టమైన కాస్ట్ల అవసరాన్ని తొలగించడం అని చెప్పింది.
మన ప్రియమైన ట్యుటోరియల్స్పాయింట్![జావాలో జెనరిక్స్: ఆచరణలో కోణ బ్రాకెట్లను ఎలా ఉపయోగించాలి - 1]()
ఆన్లైన్ జావా కంపైలర్లో కొన్ని పరీక్షల కోసం సిద్ధం చేద్దాం . మీకు ఈ క్రింది కోడ్ ఉందని అనుకుందాం:
import java.util.*;
public class HelloWorld {
public static void main(String []args) {
List list = new ArrayList();
list.add("Hello");
String text = list.get(0) + ", world!";
System.out.print(text);
}
}
ఈ కోడ్ ఖచ్చితంగా బాగా నడుస్తుంది. కానీ బాస్ మా దగ్గరకు వచ్చి "హలో, ప్రపంచం!" ఇది అతిగా ఉపయోగించబడిన పదబంధం మరియు మీరు తప్పనిసరిగా "హలో" మాత్రమే తిరిగి ఇవ్వాలి?
మేము ", ప్రపంచం!" అనే కోడ్ని తీసివేస్తాము. ఇది తగినంత ప్రమాదకరం కాదని అనిపిస్తుంది, సరియైనదా? కానీ వాస్తవానికి కంపైల్ సమయంలో మనకు లోపం వస్తుంది:
error: incompatible types: Object cannot be converted to String
సమస్య ఏమిటంటే, మా జాబితాలోని వస్తువులను నిల్వ చేస్తుంది.
స్ట్రింగ్ అనేది ఆబ్జెక్ట్ యొక్క వారసుడు (అన్ని జావా తరగతులు
ఆబ్జెక్ట్ను పరోక్షంగా వారసత్వంగా పొందుతాయి కాబట్టి ), అంటే మనకు స్పష్టమైన తారాగణం అవసరం, కానీ మేము ఒకదాన్ని జోడించలేదు. సంగ్రహణ ఆపరేషన్ సమయంలో, ఆబ్జెక్ట్ని ఉపయోగించి స్టాటిక్
String.valueOf(obj) పద్ధతిని పిలుస్తారు.
చివరికి, ఇది ఆబ్జెక్ట్ క్లాస్ యొక్క
toString పద్ధతిని పిలుస్తుంది . మరో మాటలో చెప్పాలంటే, మా
జాబితాలో ఒక
వస్తువు ఉంది . దీనర్థం మనకు నిర్దిష్ట రకం (
ఆబ్జెక్ట్ కాదు ) అవసరమైన చోట, మనమే టైప్ మార్పిడిని చేయవలసి ఉంటుంది:
import java.util.*;
public class HelloWorld {
public static void main(String []args) {
List list = new ArrayList();
list.add("Hello!");
list.add(123);
for (Object str : list) {
System.out.println("-" + (String)str);
}
}
}
అయితే, ఈ సందర్భంలో,
జాబితా వస్తువులను తీసుకుంటుంది కాబట్టి, ఇది
స్ట్రింగ్ లు మాత్రమే కాకుండా పూర్ణాంకాల లను కూడా నిల్వ చేయగలదు . కానీ చెత్త విషయం ఏమిటంటే కంపైలర్ ఇక్కడ తప్పు ఏదీ చూడలేదు. ఇప్పుడు మనం రన్ టైమ్లో ఎర్రర్ను పొందుతాము ("రన్టైమ్ ఎర్రర్" అని పిలుస్తారు). లోపం ఇలా ఉంటుంది:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
ఇది చాలా మంచిది కాదని మీరు అంగీకరించాలి. మరియు ఇదంతా ఎందుకంటే కంపైలర్ అనేది ప్రోగ్రామర్ ఉద్దేశాన్ని ఎల్లప్పుడూ సరిగ్గా అంచనా వేయగల కృత్రిమ మేధస్సు కాదు. Java SE 5 మన ఉద్దేశాలను కంపైలర్కు తెలియజేయడానికి జెనరిక్స్ని పరిచయం చేసింది — మనం ఏ రకాలను ఉపయోగించబోతున్నామో. కంపైలర్కి మనకు ఏమి కావాలో చెప్పడం ద్వారా మేము మా కోడ్ని సరిచేస్తాము:
import java.util.*;
public class HelloWorld {
public static void main(String []args) {
List<String> list = new ArrayList<>();
list.add("Hello!");
list.add(123);
for (Object str : list) {
System.out.println("-" + str);
}
}
}
మీరు చూడగలిగినట్లుగా, మాకు ఇకపై
స్ట్రింగ్కు తారాగణం అవసరం లేదు . అదనంగా, మేము రకం ఆర్గ్యుమెంట్ చుట్టూ కోణ బ్రాకెట్లను కలిగి ఉన్నాము. ఇప్పుడు కంపైలర్ మేము జాబితాకు 123 జోడించే పంక్తిని తీసివేసే వరకు తరగతిని కంపైల్ చేయడానికి అనుమతించదు, ఎందుకంటే ఇది
పూర్ణాంకం . మరియు అది మనకు అలా చెబుతుంది. చాలా మంది జనరిక్లను "సింటాక్టిక్ షుగర్" అని పిలుస్తారు. మరియు అవి సరైనవి, ఎందుకంటే జెనరిక్స్ సంకలనం చేయబడిన తర్వాత, అవి నిజంగా ఒకే రకమైన మార్పిడులుగా మారతాయి. సంకలనం చేయబడిన తరగతుల బైట్కోడ్ను చూద్దాం: స్పష్టమైన తారాగణాన్ని ఉపయోగించేది మరియు జెనరిక్స్ని ఉపయోగించేది:
![జావాలో జెనరిక్స్: ఆచరణలో కోణ బ్రాకెట్లను ఎలా ఉపయోగించాలి - 2]()
సంకలనం చేసిన తర్వాత, అన్ని జెనరిక్లు తొలగించబడతాయి. దీనిని "
టైప్ ఎరేజర్ అంటారు". టైప్ ఎరేజర్ మరియు జెనరిక్స్లు JDK యొక్క పాత వెర్షన్లతో వెనుకకు అనుకూలంగా ఉండేలా రూపొందించబడ్డాయి, అదే సమయంలో జావా యొక్క కొత్త వెర్షన్లలో టైప్ డెఫినిషన్లతో కంపైలర్ని సహాయం చేయడానికి అనుమతిస్తుంది.
ముడి రకాలు
జెనరిక్స్ గురించి చెప్పాలంటే, మనకు ఎల్లప్పుడూ రెండు వర్గాలు ఉంటాయి: పారామీటర్ చేయబడిన రకాలు మరియు ముడి రకాలు. ముడి రకాలు అనేది యాంగిల్ బ్రాకెట్లలో "టైప్ క్లారిఫికేషన్"ని విస్మరించే రకాలు:
![జావాలో జెనరిక్స్: ఆచరణలో కోణ బ్రాకెట్లను ఎలా ఉపయోగించాలి - 3]()
పారామీటర్ చేయబడిన రకాలు, చేతిలో "క్లరిఫికేషన్"ని కలిగి ఉంటాయి:
![జావాలో జెనరిక్స్: ఆచరణలో కోణ బ్రాకెట్లను ఎలా ఉపయోగించాలి - 4]()
మీరు చూడగలిగినట్లుగా, మేము స్క్రీన్షాట్లో బాణంతో గుర్తించబడిన అసాధారణ నిర్మాణాన్ని ఉపయోగించాము. ఇది జావా SE 7కి జోడించబడిన ప్రత్యేక వాక్యనిర్మాణం. దీనిని "
డైమండ్ " అంటారు. ఎందుకు? కోణ బ్రాకెట్లు వజ్రాన్ని ఏర్పరుస్తాయి:
<> .
డైమండ్ సింటాక్స్ " టైప్ ఇన్ఫరెన్స్ " భావనతో అనుబంధించబడిందని కూడా మీరు తెలుసుకోవాలి . అన్ని తరువాత, కంపైలర్, చూడటం
<>కుడి వైపున, అసైన్మెంట్ ఆపరేటర్ యొక్క ఎడమ వైపు చూస్తుంది, ఇక్కడ అది విలువ కేటాయించబడుతున్న వేరియబుల్ రకాన్ని కనుగొంటుంది. ఈ భాగంలో కనుగొనే దాని ఆధారంగా, అది కుడి వైపున ఉన్న విలువ యొక్క రకాన్ని అర్థం చేసుకుంటుంది. నిజానికి, ఒక సాధారణ రకం ఎడమవైపు ఇవ్వబడితే, కానీ కుడివైపున ఇవ్వకపోతే, కంపైలర్ రకాన్ని ఊహించవచ్చు:
import java.util.*;
public class HelloWorld {
public static void main(String []args) {
List<String> list = new ArrayList();
list.add("Hello, World");
String data = list.get(0);
System.out.println(data);
}
}
కానీ ఇది కొత్త స్టైల్ని జెనరిక్స్తో మరియు పాత స్టైల్ని అవి లేకుండా మిక్స్ చేస్తుంది. మరియు ఇది చాలా అవాంఛనీయమైనది. ఎగువ కోడ్ను కంపైల్ చేస్తున్నప్పుడు, మనకు ఈ క్రింది సందేశం వస్తుంది:
Note: HelloWorld.java uses unchecked or unsafe operations
వాస్తవానికి, మీరు ఇక్కడ వజ్రాన్ని ఎందుకు జోడించాలి అనేది అపారమయినదిగా అనిపిస్తుంది. కానీ ఇక్కడ ఒక ఉదాహరణ:
import java.util.*;
public class HelloWorld {
public static void main(String []args) {
List<String> list = Arrays.asList("Hello", "World");
List<Integer> data = new ArrayList(list);
Integer intNumber = data.get(0);
System.out.println(data);
}
}
సేకరణను ఆర్గ్యుమెంట్గా తీసుకునే రెండవ కన్స్ట్రక్టర్ని
అర్రేలిస్ట్ కలిగి ఉందని మీరు గుర్తుచేసుకుంటారు . మరియు ఇక్కడే ఏదో చెడు దాగి ఉంది. డైమండ్ సింటాక్స్ లేకుండా, కంపైలర్ మోసపోతున్నాడని అర్థం కాలేదు. డైమండ్ సింటాక్స్తో, ఇది చేస్తుంది. కాబట్టి, రూల్ #1: ఎల్లప్పుడూ పారామీటర్ చేయబడిన రకాలతో డైమండ్ సింటాక్స్ని ఉపయోగించండి. లేకపోతే, మేము ముడి రకాలను ఎక్కడ ఉపయోగిస్తున్నామో మిస్ అయ్యే ప్రమాదం ఉంది. "చెక్ చేయని లేదా అసురక్షిత కార్యకలాపాలను ఉపయోగిస్తుంది" హెచ్చరికలను తొలగించడానికి, మేము పద్ధతి లేదా తరగతిపై
@SuppressWarnings("చెక్ చేయబడలేదు") ఉల్లేఖనాన్ని ఉపయోగించవచ్చు . కానీ మీరు దీన్ని ఎందుకు ఉపయోగించాలని నిర్ణయించుకున్నారో ఆలోచించండి. మొదటి నియమాన్ని గుర్తుంచుకోండి. బహుశా మీరు టైప్ ఆర్గ్యుమెంట్ని జోడించాల్సి ఉంటుంది.
జావా జెనరిక్ పద్ధతులు
పారామీటర్ రకాలు మరియు రిటర్న్ రకం పారామితి చేయబడిన పద్ధతులను సృష్టించడానికి జెనరిక్స్ మిమ్మల్ని అనుమతిస్తాయి. ఒరాకిల్ ట్యుటోరియల్లో ఈ సామర్థ్యానికి ప్రత్యేక విభాగం కేటాయించబడింది: "
జనరిక్ మెథడ్స్ ". ఈ ట్యుటోరియల్లో బోధించిన వాక్యనిర్మాణాన్ని గుర్తుంచుకోవడం ముఖ్యం:
- ఇది యాంగిల్ బ్రాకెట్లలోని టైప్ పారామితుల జాబితాను కలిగి ఉంటుంది;
- పద్ధతి యొక్క రిటర్న్ రకానికి ముందు టైప్ పారామితుల జాబితా వెళుతుంది.
ఒక ఉదాహరణ చూద్దాం:
import java.util.*;
public class HelloWorld {
public static class Util {
public static <T> T getValue(Object obj, Class<T> clazz) {
return (T) obj;
}
public static <T> T getValue(Object obj) {
return (T) obj;
}
}
public static void main(String []args) {
List list = Arrays.asList("Author", "Book");
for (Object element : list) {
String data = Util.getValue(element, String.class);
System.out.println(data);
System.out.println(Util.<String>getValue(element));
}
}
}
మీరు Util తరగతిని చూస్తే , దానికి రెండు సాధారణ పద్ధతులు ఉన్నాయని మీరు చూస్తారు. టైప్ ఇన్ఫరెన్స్ యొక్క అవకాశం కారణంగా, మేము కంపైలర్కు నేరుగా రకాన్ని సూచించవచ్చు లేదా మనమే దానిని పేర్కొనవచ్చు. రెండు ఎంపికలు ఉదాహరణలో ప్రదర్శించబడ్డాయి. మార్గం ద్వారా, మీరు దాని గురించి ఆలోచిస్తే వాక్యనిర్మాణం చాలా అర్ధమే. సాధారణ పద్ధతిని ప్రకటించేటప్పుడు, మేము పద్ధతికి ముందు టైప్ పారామీటర్ని పేర్కొంటాము, ఎందుకంటే మేము పద్ధతి తర్వాత టైప్ పరామితిని ప్రకటిస్తే, JVM ఏ రకాన్ని ఉపయోగించాలో గుర్తించలేకపోతుంది.
దీని ప్రకారం, మేము మొదట T రకం పరామితిని ఉపయోగిస్తామని ప్రకటిస్తాము , ఆపై మేము ఈ రకాన్ని తిరిగి ఇవ్వబోతున్నామని చెబుతాము. సహజంగానే,
Util.<Integer>getValue(element, String.class) లోపంతో విఫలమవుతుంది:
అననుకూల రకాలు: Class<String>ని Class<Integer>కి మార్చడం సాధ్యం కాదు . సాధారణ పద్ధతులను ఉపయోగిస్తున్నప్పుడు, మీరు ఎల్లప్పుడూ టైప్ ఎరేజర్ గుర్తుంచుకోవాలి. ఒక ఉదాహరణ చూద్దాం:
import java.util.*;
public class HelloWorld {
public static class Util {
public static <T> T getValue(Object obj) {
return (T) obj;
}
}
public static void main(String []args) {
List list = Arrays.asList(2, 3);
for (Object element : list) {
System.out.println(Util.<Integer>getValue(element) + 1);
}
}
}
ఇది బాగానే నడుస్తుంది. కానీ కంపైలర్ అర్థం చేసుకున్నంత కాలం మాత్రమే, పిలవబడే పద్ధతి యొక్క రిటర్న్ రకం
పూర్ణాంకం . కన్సోల్ అవుట్పుట్ స్టేట్మెంట్ను క్రింది లైన్తో భర్తీ చేయండి:
System.out.println(Util.getValue(element) + 1);
మాకు లోపం వస్తుంది:
bad operand types for binary operator '+', first type: Object, second type: int.
మరో మాటలో చెప్పాలంటే, టైప్ ఎరేజర్ సంభవించింది. కంపైలర్ ఎవరూ రకాన్ని పేర్కొనలేదని చూస్తారు, కాబట్టి రకం
ఆబ్జెక్ట్గా సూచించబడుతుంది మరియు పద్ధతి లోపంతో విఫలమవుతుంది.
సాధారణ తరగతులు
పద్ధతులు మాత్రమే పారామితి చేయబడవు. తరగతులు కూడా చేయవచ్చు.
ఒరాకిల్ యొక్క ట్యుటోరియల్ యొక్క "జనరిక్ రకాలు" విభాగం దీనికి అంకితం చేయబడింది. ఒక ఉదాహరణను పరిశీలిద్దాం:
public static class SomeType<T> {
public <E> void test(Collection<E> collection) {
for (E element : collection) {
System.out.println(element);
}
}
public void test(List<Integer> collection) {
for (Integer element : collection) {
System.out.println(element);
}
}
}
ఇక్కడ ప్రతిదీ సులభం. మేము సాధారణ తరగతిని ఉపయోగిస్తే, తరగతి పేరు తర్వాత రకం పరామితి సూచించబడుతుంది.
ఇప్పుడు ప్రధాన పద్ధతిలో ఈ తరగతి యొక్క ఉదాహరణను సృష్టిద్దాం :
public static void main(String []args) {
SomeType<String> st = new SomeType<>();
List<String> list = Arrays.asList("test");
st.test(list);
}
ఈ కోడ్ బాగా రన్ అవుతుంది.
కంపైలర్ సంఖ్యల
జాబితా మరియు స్ట్రింగ్ల
సేకరణ ఉన్నట్లు చూస్తుంది . మేము టైప్ పరామితిని తొలగించి ఇలా చేస్తే ఏమి చేయాలి:
SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
మాకు లోపం వస్తుంది:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
మళ్ళీ, ఇది రకం ఎరేజర్. తరగతి ఇకపై టైప్ పరామితిని ఉపయోగించనందున, కంపైలర్ మేము జాబితాను ఆమోదించినందున
, జాబితా<Integer> తో ఉన్న పద్ధతి చాలా సముచితమని నిర్ణయిస్తుంది. మరియు మేము ఒక లోపంతో విఫలమవుతాము. కాబట్టి, మాకు రూల్ #2 ఉంది: మీకు సాధారణ తరగతి ఉంటే, ఎల్లప్పుడూ టైప్ పారామితులను పేర్కొనండి.
పరిమితులు
మేము సాధారణ పద్ధతులు మరియు తరగతులలో పేర్కొన్న రకాలను పరిమితం చేయవచ్చు.
ఉదాహరణకు, మేము కంటైనర్లో ఒక సంఖ్యను మాత్రమే టైప్ ఆర్గ్యుమెంట్గా అంగీకరించాలని అనుకుందాం . ఈ ఫీచర్ ఒరాకిల్ ట్యుటోరియల్లోని
బౌండెడ్ టైప్ పారామీటర్స్ విభాగంలో వివరించబడింది . ఒక ఉదాహరణ చూద్దాం:
import java.util.*;
public class HelloWorld {
public static class NumberContainer<T extends Number> {
private T number;
public NumberContainer(T number) { this.number = number; }
public void print() {
System.out.println(number);
}
}
public static void main(String []args) {
NumberContainer number1 = new NumberContainer(2L);
NumberContainer number2 = new NumberContainer(1);
NumberContainer number3 = new NumberContainer("f");
}
}
మీరు చూడగలిగినట్లుగా, మేము టైప్ పారామీటర్ని
నంబర్ క్లాస్/ఇంటర్ఫేస్ లేదా దాని వారసులకు పరిమితం చేసాము. మీరు తరగతిని మాత్రమే కాకుండా ఇంటర్ఫేస్లను కూడా పేర్కొనవచ్చని గమనించండి. ఉదాహరణకి:
public static class NumberContainer<T extends Number & Comparable> {
జనరిక్లు వైల్డ్కార్డ్లకు కూడా మద్దతు ఇస్తాయి అవి మూడు రకాలుగా విభజించబడ్డాయి:
మీ వైల్డ్కార్డ్ల ఉపయోగం
గెట్-పుట్ సూత్రానికి కట్టుబడి ఉండాలి . దీనిని ఈ క్రింది విధంగా వ్యక్తీకరించవచ్చు:
- మీరు నిర్మాణం నుండి విలువలను మాత్రమే పొందినప్పుడు పొడిగించిన వైల్డ్కార్డ్ని ఉపయోగించండి .
- మీరు నిర్మాణంలో విలువలను మాత్రమే ఉంచినప్పుడు సూపర్ వైల్డ్కార్డ్ని ఉపయోగించండి .
- మరియు మీరిద్దరూ నిర్మాణాన్ని పొందాలనుకున్నప్పుడు మరియు ఉంచాలనుకున్నప్పుడు వైల్డ్కార్డ్ని ఉపయోగించవద్దు.
ఈ సూత్రాన్ని ప్రొడ్యూసర్ ఎక్స్టెండ్స్ కన్స్యూమర్ సూపర్ (PECS) సూత్రం అని కూడా అంటారు.
జావా యొక్క Collections.copy పద్ధతికి సోర్స్ కోడ్ నుండి ఇక్కడ ఒక చిన్న ఉదాహరణ :
![జావాలో జనరిక్స్: ఆచరణలో కోణ బ్రాకెట్లను ఎలా ఉపయోగించాలి - 5]()
మరియు ఇక్కడ ఏమి పని చేయదు అనేదానికి ఒక చిన్న ఉదాహరణ ఉంది:
public static class TestClass {
public static void print(List<? extends String> list) {
list.add("Hello, World!");
System.out.println(list.get(0));
}
}
public static void main(String []args) {
List<String> list = new ArrayList<>();
TestClass.print(list);
}
కానీ మీరు
పొడిగింపులను సూపర్తో భర్తీ చేస్తే,
అంతా బాగానే ఉంది.
మేము జాబితాను దాని కంటెంట్లను ప్రదర్శించే ముందు విలువతో నింపాము కాబట్టి , అది
వినియోగదారు . దీని ప్రకారం, మేము సూపర్ ఉపయోగిస్తాము.
వారసత్వం
జెనరిక్స్ మరొక ఆసక్తికరమైన లక్షణాన్ని కలిగి ఉంది: వారసత్వం. ఒరాకిల్ యొక్క ట్యుటోరియల్లో
జెనరిక్స్, ఇన్హెరిటెన్స్ మరియు సబ్టైప్స్ క్రింద జెనరిక్స్ కోసం వారసత్వం పనిచేసే విధానం వివరించబడింది . ముఖ్యమైన విషయం ఏమిటంటే ఈ క్రింది వాటిని గుర్తుంచుకోవడం మరియు గుర్తించడం. మేము దీన్ని చేయలేము:
List<CharSequence> list1 = new ArrayList<String>();
ఎందుకంటే వారసత్వం జెనరిక్స్తో విభిన్నంగా పనిచేస్తుంది:
![జావాలో జెనరిక్స్: ఆచరణలో కోణ బ్రాకెట్లను ఎలా ఉపయోగించాలి - 6]()
మరియు లోపంతో విఫలమయ్యే మరొక మంచి ఉదాహరణ ఇక్కడ ఉంది:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
మళ్ళీ, ఇక్కడ ప్రతిదీ సులభం. String అనేది ఆబ్జెక్ట్ యొక్క వారసుడు అయినప్పటికీ,
List<String> జాబితా < Object> యొక్క వారసుడు కాదు .
మీరు నేర్చుకున్న వాటిని బలోపేతం చేయడానికి, మా జావా కోర్సు నుండి వీడియో పాఠాన్ని చూడమని మేము మీకు సూచిస్తున్నాము
ముగింపు
కాబట్టి మేము జెనరిక్స్ గురించి మా మెమరీని రిఫ్రెష్ చేసాము. మీరు అరుదుగా వారి సామర్థ్యాలను పూర్తిగా ఉపయోగించుకుంటే, కొన్ని వివరాలు అస్పష్టంగా పెరుగుతాయి. ఈ చిన్న సమీక్ష మీ జ్ఞాపకశక్తిని పెంచడంలో సహాయపడిందని నేను ఆశిస్తున్నాను. ఇంకా మెరుగైన ఫలితాల కోసం, ఈ క్రింది మెటీరియల్తో మిమ్మల్ని మీరు పరిచయం చేసుకోవాలని నేను గట్టిగా సిఫార్సు చేస్తున్నాను: