I'm working on my abstract swt mvc controller noob-class:
Here are code's snippets:
public abstract class Controller {
protected View view;
public Controller(View v) {
view = v;
}
protected void render() {
data();
view.setData(data);
view.render();
listeners();
if (display)
view.open();
}
protected void data() {}
protected void listeners() {}
}
AboutController.java (represends new window):
public class AboutController extends Controller {
public AboutController() {
super(new AboutView());
super.render();
}
}
And I've faced with an obstacle: in abstract contoller i have a field View view, but it's child classes can represend a sub-class of View (like AboutView that extends View), and I cant access to non-View fields and methods (that are new in AboutView).
Can i clarify the class's field view in it's child-classes?
I understand that most likely this is a bug in my classes's architecture, but i cant figure out what to do.
The best / most idiomatic way is to use Generics.
public abstract class Controller<T extends View> {
protected T view;
public Controller(T v) {
view = v;
}
protected void render() {
data();
view.setData(data);
view.render();
listeners();
if (display)
view.open();
}
protected void data() {}
protected void listeners() {}
}
public class AboutController extends Controller<AboutView> {
public AboutController() {
super(new AboutView());
super.render();
}
public void doSomething() {
view.getAboutStuff();
}
}
Related
I'm trying to adjust the observer pattern to my code, so in my case MSG0100 it's my Subject class that it updates the variable msg100PreselectionAplication, and the MainActivity class it's an concrete observer, and usually you have to create the Observer class, but my problem is that the MainActivity has to extend from that class and that's a problem because Java does not support multiple inheritance. Here is what i have until now, can some one give me an idea of what i need next? Please
MSG0100 class:
public class MSG0100 implements PreselectionAplicationUseCases {
private OnMsg100PreselectionChanged listener = null;
public void setOnMsgPreselectionChanged(OnMsg100PreselectionChanged listener) {
this.listener = listener;
}
public void setMsg100PreselectionAplication(boolean msg100PreselectionAplication) {
if(listener != null) {
listener.onPreselectionChanged(msg100PreselectionAplication);
}
}
}
interface OnMsg100PreselectionChanged {
void onPreselectionChanged(boolean isChanged);
}
Main activity class:
public class MainActivity extends AppCompatActivity {
private ExpandableListViewAdapter mExpandableListViewAdapter;
PreselectionAplicationUseCases preselectionAplicationUseCases;
public void Preselection(){
preselectionApplicationUseCases.setOnMsgPreselectionChanged(new OnMsg100PreselectionChanged {
#Override
void onPreselectionChanged(boolean isChanged) {
//do something with changed boolean
}
});
}
}
You should create Observer as an Interface which implemented by MainActivity like this:
interface Observer {
void update(Data data)
}
public class MainActivity extends AppCompatActivity implements Observer {
//…
#Override
public void update(Data data) {
// update your data to your UI here
}
//…
}
and modify your subject to receive observer
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
public class MSG0100 implements PreselectionAplicationUseCases, Subject {
private Data data = new Data();
private Observer o;
#Override
public void registerObserver(Observer o) {
this.o = o;
}
#Override
public void removeObserver(Observer o) {
o = null;
}
#Override
public void notifyObservers() {
o.update(data);
}
// ... other method that can update the data class
}
that is an Observer Pattern, but the way I see, you simply want to implement OnMsg100PreselectionChanged on Activity like:
public class MainActivity extends AppCompatActivity, OnMsg100PreselectionChanged {
void someMethod() {
MSG0100 msg0100 = new MSG0100();
msg0100.setOnMsgPreselectionChanged(this);
}
#Override
void onPreselectionChanged(boolean isChanged) {
}
}
I've seen two approaches to MVP pattern in Android. Both are used in Android Architecture Blueprints:
public interface Contract {
interface View {
void showData(String data);
}
interface StartVersionPresenter {
void start();
}
interface DropViewVersionPresenter {
void takeView(View view);
void dropView();
}
}
1) The presenter in which the view is injected via the constructor:
public class StartVersionPresenter implements Contract.StartVersionPresenter {
private final Contract.View view;
private final Repository repository;
public StartVersionPresenter(Contract.View view, Repository repository) {
this.view = view;
this.repository = repository;
}
#Override
public void start() {
loadData();
}
private void loadData() {
repository.getData(new DataCallback() {
#Override
public void onDataLoaded(String someData) {
view.showData(someData);
}
});
}
}
start() is called in onResume() Fragment's method.
2) The presenter in which the view is injected via the method (takeView in my example):
public class DropViewVersionPresenter implements Contract.DropViewVersionPresenter {
private final Repository repository;
private Contract.View view;
public DropViewVersionPresenter(Repository repository) {
this.repository = repository;
}
#Override
public void takeView(Contract.View view) {
this.view = view;
loadData();
}
#Override
public void dropView() {
view = null;
}
private void loadData() {
repository.getData(new DataCallback() {
#Override
public void onDataLoaded(String someData) {
if (view != null)
view.showData(someData);
}
});
}
}
takeView(Contract.View view) is called in Fragment's onResume() method.
dropView() is called in Fragment's onDestroy() method.
In both cases the presenter is created in Fragment's onCreate() method.
Why does the second approach is used in more cases rather then the first one? I can see that the first one is simpler because we don't have to check that the view is not null if we want to call the method on it.
In a project that is an Activity Model, Presenter and Model, the Activity Theme and Presenter and Presenter Model. When I do #Inject in Presenter to instantiate the Model it is never instantiated.
Do you need a dependency "cascade"?
FATAL EXCEPTION: main Process: fipedaggerrxjava, PID: 22258
java.lang.NullPointerException: Attempt to invoke interface method
'void
fipedaggerrxjava.mvp.SelectMarcaContractMVP$Model.getMarcas(java.lang.String)'
on a null object reference at
fipedaggerrxjava.module.marca.MarcaPresenter.initData(MarcaPresenter.java:35)
at
fipedaggerrxjava.module.marca.MarcaActivity$1.onCheckedChanged(MarcaActivity.java:63)
I already checked in Debug and really the Model that is not being instantiated by Dagger but I can not understand why.
App
public class App extends Application implements HasActivityInjector{
#Inject
public DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;
#Override
public void onCreate() {
super.onCreate();
DaggerAppComponent.builder().build().inject(App.this);
}
#Override
public AndroidInjector<Activity> activityInjector() {
return activityDispatchingAndroidInjector;
}
}
ActivityBuilder
#Module
public abstract class ActivityBuilder {
#Binds
#IntoMap
#ActivityKey(MarcaActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindMarcaActivity (MarcaComponent.Builder builder);
}
AppComponent
#Component(modules = {ActivityBuilder.class, AndroidInjectionModule.class, AppModule.class})
#Singleton
public interface AppComponent {
void inject(App app);
}
AppModule
#Module(subcomponents = MarcaComponent.class)
public class AppModule {
#Provides
#Singleton
#Named("URL_MARCA")
String provideStringURLBase(){
return "https://fipe.parallelum.com.br/api/v1/";
}
#Provides
#Singleton
Context provideContext(App app){
return app;
}
#Provides
#Singleton
Gson provideGsonRepositorie(){
return new GsonBuilder()
.create();
}
#Singleton
#Provides
OkHttpClient provideOkHttpCliente1(){
return new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.build();
}
#Singleton
#Provides
RxJavaCallAdapterFactory provideRxJavaCallAdapterFactory(){
return RxJavaCallAdapterFactory.create();
}
#Provides
#Singleton
Retrofit provideRetrofit(OkHttpClient okHttpClient, Gson gson, RxJavaCallAdapterFactory rxAdapter, #Named("URL_MARCA") String stringBaseURL){
return new Retrofit.Builder()
.baseUrl(stringBaseURL)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(rxAdapter)
.client(okHttpClient)
.build();
}
}
MarcaComponent
#Subcomponent(modules = MarcaModule.class)
#PerMarca
public interface MarcaComponent extends AndroidInjector<MarcaActivity>{
#Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MarcaActivity> {}
}
MarcaModule
#Module
public class MarcaModule{
#Provides
#PerMarca
APIFIPE provideAPIFIPE(Retrofit retrofit){
return retrofit.create(APIFIPE.class);
}
#Provides
#PerMarca
View provideViewMarca(MarcaActivity activity){
return activity;
}
#Provides
#PerMarca
Presenter providePresenterMarca(){
return new MarcaPresenter();
}
#Provides
#PerMarca
Model provideModelMarca(){
return new MarcaModel();
}
}
AdapterMarca
public class AdapterMarca extends BaseAdapter {
private List<Marca> mListMarca;
#Inject
public Context mContext;
public AdapterMarca(List<Marca> listMarca){
this.mListMarca = listMarca;
}
#Override
public int getCount() {
return mListMarca.size();
}
#Override
public Object getItem(int position) {
return mListMarca.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = LayoutInflater.from(mContext).inflate(R.layout.layout_list_item, parent, false);
TextView tvNome = view.findViewById(R.id.tv_marca);
tvNome.setText(mListMarca.get(position).getName().toString());
return view;
}
public void addListMarca(List<Marca> marcaList){
mListMarca.clear();
mListMarca.addAll(marcaList);
notifyDataSetChanged();
}
}
MarcaActivity
public class MarcaActivity extends BaseActivity implements HasActivityInjector, View {
private RadioGroup radioGroupMarca;
private String tipoSelect = "";
private List<Marca> mListMarca;
private AdapterMarca mAdapterMarca;
private ListView listViewMarca;
#Inject
public Presenter mMarcaPresenter;
#Inject
protected DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;
#Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(MarcaActivity.this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listViewMarca = findViewById(R.id.lv_marca);
radioGroupMarca = findViewById(R.id.rg_tipo);
radioGroupMarca.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
int id = group.getCheckedRadioButtonId();
switch (id){
case R.id.rb_carros : tipoSelect = "carros";
mMarcaPresenter.initData(tipoSelect);
break;
case R.id.rb_motos : tipoSelect = "motos";
mMarcaPresenter.initData(tipoSelect);
break;
case R.id.rb_caminhoes : tipoSelect = "caminhoes";
mMarcaPresenter.initData(tipoSelect);
break;
}
}
});
}
#Override
public AndroidInjector<Activity> activityInjector() {
return activityDispatchingAndroidInjector;
}
#Override
public void onMarcaLoader(List<Marca> listMarcas) {
if(mListMarca==null && listMarcas!=null){
initListView();
}
if(mAdapterMarca!=null){
mListMarca.clear();
mListMarca = listMarcas;
mAdapterMarca.addListMarca(mListMarca);
}
}
private void initListView(){
mAdapterMarca = new AdapterMarca(mListMarca);
listViewMarca.setAdapter(mAdapterMarca);
}
}
MarcaPresenter
#PerMarca
public class MarcaPresenter implements Presenter {
#Inject
View mMarcaView;
#Inject
Model mMarcaModel;
#Inject
public MarcaPresenter(){
}
#Override
public void initData(String tipoMarca) {
mMarcaModel.getMarcas(tipoMarca);
}
#Override
public void getMarcas(List<Marca> listMarcas) {
mMarcaView.onMarcaLoader(listMarcas);
}
#Override
public void onShowDialog(String title, String msg) {
mMarcaView.onShowDialog(title, msg);
}
#Override
public void onHideShowDialog() {
mMarcaView.onHideShowDialog();
}
#Override
public void onShowToast(String s) {
mMarcaView.onShowToast(s);
}
}
MarcaModel
#PerMarca
public class MarcaModel implements Model {
#Inject
APIFIPE mApiFIPE;
#Inject
Presenter mMarcaPresenter;
#Inject
public MarcaModel(){
}
#Override
public void getMarcas(String tipoVeiculo) {
final List<Marca> marcaList = new ArrayList<>();
Observable<List<Marca>> observable = mApiFIPE.getRepositories(tipoVeiculo);
observable.subscribe(new Observer<List<Marca>>() {
#Override
public void onCompleted() {
mMarcaPresenter.getMarcas(marcaList);
}
#Override
public void onError(Throwable e) {
mMarcaPresenter.onShowDialog("Erro", "Falha ao carregar lista de marcas");
}
#Override
public void onNext(List<Marca> marcas) {
marcaList.addAll(marcas);
}
});
}
}
In below lines:
public void onCompleted() {
mMarcaPresenter.getMarcas(marcaList);
}
-You trying to use mMarcePResenter and you inject it but you still need to use the App's component get assigned to mMarcaPresenter.
Initialize the injected value in below constructor:
#Inject
public MarcaModel(){
mMarcaPresenter =
}
You have declared provider methods(in MarcaModule) for your model and presenter classes. Dagger uses those provider methods, ending up not supplying the field injected values.
Therefore, you need to supply your dependencies in your module(s), i.e. via Model and Presenter constructors.
MarcaModel
...
Presenter mMarcaPresenter;
#Inject
public MarcaModel(Presenter presenter){
mMarcaPresenter = presenter
}
...
MarcaModule
#Module
public class MarcaModule{
...
#Provides
#PerMarca
Model provideModelMarca(Presenter presenter){
return new MarcaModel(presenter);
}
}
Reading Dagger user's guide carefully might help you even more;
https://google.github.io/dagger/users-guide
I have already looked up many answers here about this issue but for the life of me I can't seem to fix this problem on my end and I need some help.
BasePresenter:
public abstract class BasePresenter<V> {
private V mView;
public void attachView(V view) { mView = view; }
public void detachView() { mView = null; }
}
BaseFragment:
public abstract class BaseFragment<P extends BasePresenter> extends Fragment {
#Inject protected P mPresenter;
#Override
public void onResume() {
super.onResume();
mPresenter.attachView(this); // unchecked call to 'attachView(V)' as a member of raw type 'BasePresenter'
}
#Override
public void onPause() {
super.onPause();
mPresenter.detachView();
}
}
MyPresenter:
public class MyPresenter extends BasePresenter<MyPresenter.MyView> {
#Inject
public MyPresenter() {}
public interface MyView {}
}
MyFragment:
public class MyFragment extends BaseFragment implements MyPresenter.MyView {}
The problem is in the type-variable declaration:
class BaseFragment<P extends BasePresenter>
BasePresenter is a generic class, so you need to specify what its type parameter is. From this snippet:
mPresenter.attachView(this);
It would seem that you expect BaseFragment to be bound to the type-variable V in BasePresenter - so I would rewrite your BaseFragment declaration as follows:
abstract class BaseFragment<P extends BasePresenter<BaseFragment<P>>> { ... }
That should take care of the unchecked warning. That said, I strongly suspect that what you are really after is something like this:
abstract class BaseFragment<P extends BasePresenter<V>, V> { ... }
Where V is an independent type-variable modelling the 'view'.
Simple, instead of
public abstract class BaseFragment<P extends BasePresenter> extends Fragment {
It should be
public abstract class BaseFragment<V, P extends BasePresenter<V>> extends Fragment {
or
public abstract class BaseFragment<P extends BasePresenter<BaseFragment<P>>> extends Fragment {
or
public abstract class BaseFragment<V extends BaseFragment<V, P>, P extends BasePresenter<V>> extends Fragment {
Basically, make sure the BasePresenter is parametrized with something.
EDIT:
Okay, based on what you're actually trying to do, you should do it like this:
public abstract class BasePresenter<V> {
private V mView;
public void attachView(V view) { mView = view; }
public void detachView() { mView = null; }
}
public abstract class BaseFragment<V extends BaseFragment<V, P>, P extends BasePresenter<V>> extends Fragment {
protected abstract P getPresenter();
#Override
public void onResume() {
super.onResume();
getPresenter().attachView(this); // unchecked call to 'attachView(V)' as a member of raw type 'BasePresenter'
}
#Override
public void onPause() {
super.onPause();
getPresenter().detachView();
}
}
public class MyPresenter extends BasePresenter<MyPresenter.MyView> {
#Inject
public MyPresenter() {}
public interface MyView {}
}
public class MyFragment extends BaseFragment<MyFragment, MyPresenter> implements MyPresenter.MyView {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
MyFragmentComponent component = ((MainActivity)getActivity()).getComponent().myFragmentComponent();
component.inject(this);
View view = inflater.inflate(R.layout.blah, container, false);
ButterKnife.bind(this, view);
return view;
}
}
EDIT2: Based on provided example:
public class RexTester {
// MAIN CLASS
static class Rextester {
public static void main(String args[]) {
new MyFragment();
}
}
// MVP CODE
interface BaseView {}
final static class MyPresenter extends BasePresenter<MyPresenter.MyView> {
public MyPresenter() {}
public void executeAction() {
mView.onCallback();
}
interface MyView extends BaseView {
void onCallback();
}
}
abstract static class BasePresenter<V extends BaseView> {
protected V mView;
public void attachView(V view) { mView = view;}
public void detachView() { mView = null; }
}
final static class MyFragment extends BaseFragment<MyPresenter.MyView, MyPresenter> implements MyPresenter.MyView {
private MyPresenter mPresenter;
public MyFragment() {
mPresenter = new MyPresenter();
onResume(); // Mock onResume() lifecycle event!
mPresenter.executeAction();
onPause(); // Mock onPause() lifecycle event!
}
protected MyPresenter getPresenter() {
return mPresenter;
}
#Override
protected MyPresenter.MyView getThis() {
return this;
}
public void onCallback() {
System.out.println("Hello AndroidMVP!");
}
}
abstract static class BaseFragment<V extends BaseView, P extends BasePresenter<V>> extends Fragment implements BaseView {
protected abstract P getPresenter();
protected void onResume() {
super.onResume();
getPresenter().attachView(getThis());
}
protected abstract V getThis();
protected void onPause() {
super.onPause();
getPresenter().detachView();
}
}
// ANDROID FRAMEWORK MOCK
abstract static class Fragment {
protected void onResume() {}
protected void onPause() {}
}
}
The title may be bit confusing but here is what I am facing
I have a class:
public abstract class BaseFragmentActivity<T> extends FragmentActivity {
static final int PROGRESS_DIALOG = 0;
Dialog progessDialog;
public abstract void displayData(T output);
#Override
protected Dialog onCreateDialog(int id) {
if (id == PROGRESS_DIALOG) {
ProgressDialog progressDialog = ProgressDialog.show(this, "",
"Loading. Please wait...", true);
progessDialog = progressDialog;
}
return progessDialog;
}
class PerformOPTask extends AsyncTask<Void, String, T> {
// connector=new JSONConnector();
Connector connector;
String curUrl;
Class<T> clazz;
PerformOPTask(String url, Class<T> curClazz) {
//connector = new UnitTestConnector();
connector = new JSONConnector();
curUrl = url;
clazz = curClazz;
}
#Override
protected T doInBackground(Void... params) {
return connector.getData(URLUtils.getFormattedUrl(curUrl),clazz);
}
#Override
protected void onPostExecute(T output) {
displayData(output);
}
}
}
Then I have a subclass as :
public abstract class BaseListFragmentActivity<T> extends BaseFragmentActivity<T> implements OnItemClickListener, OnClickListener{
protected ListView mList;
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.table_list);
CommonUtil.getActionBarWithBackButton(this,getLayoutInflater());
mList=(ListView)findViewById(R.id.table_list_listView);
mList.setOnItemClickListener(this);
}
public void onBackABButtonPressed(View view) {
finish();
}
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
#Override
public abstract void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3);
}
Now I am extending this class as below:
public class ListAccountsActivity<T> extends BaseListFragmentActivity<AccountData> {
protected Acct[] mItems;
private String[] mIcons;
protected boolean displayHandledBySubClass=false;
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
new PerformOPTask(getString(R.string.get_account),AccountData.class).execute();
showDialog(PROGRESS_DIALOG);
//.getData(URLUtils.getFormattedUrl(getString(R.string.get_account)),actData);
}
#Override
public void onItemClick(AdapterView<?> lv, View view, int position, long id) {
// super.onListItemClick(l, v, position, id);
// Get the item that was clicked
Acct account = (Acct) mList.getAdapter().getItem(position);
Intent intent=new Intent(this,AccountDetailViewActivity.class);
intent.putExtra("selectedAccount",account);
startActivity(intent);
}
#Override
public void displayData(AccountData output){
if(displayHandledBySubClass){
//handle display in subclass
handleDisplayData(output);
}
else {
Acct[] accountArray = new Acct[output.getAccount().size()];
mItems = output.getAccount().toArray(accountArray);
IWMArrayAdapter<Acct> adapter = new IWMArrayAdapter<Acct>(this, mItems);
adapter.setIcons(mIcons);
adapter.setArrowNeeded();
//mList is superClassVariable
mList.setAdapter(adapter);
dismissDialog(PROGRESS_DIALOG);
adapter.notifyDataSetChanged();
}
}
public void handleDisplayData(T output){
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
//Toast.makeText(this, "Tapped search", Toast.LENGTH_SHORT).show();
super.onCreateOptionsMenu(menu);
MenuInflater menuInflater = getMenuInflater();
menuInflater.inflate(R.menu.list_servers_menu, menu);
// Calling super after populating the menu is necessary here to ensure
// that the
// action bar helpers have a chance to handle this event.
return true;
}
}
My Question is can I make handleDisplayData generic in some way so that I can pass any type to it. What I am trying to do is to reuse logic in BaseListFragmentActivity as much as possible and handle the only task specific to ListAccountsActivity or its subclass in that class/subclass.
I hope my question is clear, Thanks for any help
If I understand correctly, you want to be able to use type-specific methods from the base in the subclass, and for that you need to make everything generic:
public abstract class GenericBase<T> { ... }
public abstract class ExtendedGeneric<T> extends GenericBase<T> { ... }
public class ExtendedGenericSub<T> extends ExtendedGeneric<T> { ... }
Point being that if ExtendedGeneric extends GenericBase<DataOutput>, only methods of GenericBase<DataOutput> are accessible from ExtendedGeneric.
Is this what you want?
public abstract class ExtendedGeneric<C> extends GenericBase<DataOutput> {
boolean handleInSub;
#Override
public void displayData(DataOutput t) {
if(handleInSub){
handleInSubClass(getValue(t));
}
//handle here
System.out.println(t);
}
protected abstract void handleInSubClass(C c);
protected abstract C getValue(DataOutput t);
}
This is of course only assumes that the data type C will come from DataOutput t. The idea is you can also make the ExtendenGeneric parameterized so you can make the classes extending it controls the datatype supplied to handleInSubClass.
Sure you can:
GenericParent
public abstract class GenericParent<T> {
public abstract void displayData(T t);
}
GenericChild
public class GenericChild<T> extends GenericParent<GenericChild> {
#Override
public void displayData(GenericChild t) {
// Do Something Here...
}
/**
* Using protected better than public,
* to prevent other class access directly to this method.
* But make sure this the other class is not same package with this GenericChild,
* because if it same package than the other class can access directly this method.
**/
protected void handleSubClass(T t){}
}
SubClass
public class SubClass extends GenericChild<SubClass> {
#Override
public void handleSubClass(SubClass t){
// Do Something Here...
}
}
Are we talking about this kind of thing?
class Vehicle {};
abstract class RoadVehicle extends Vehicle {
abstract int getNumberOfWheels();
}
class Truck extends RoadVehicle {
int getNumberOfWheels() {
return 8;
}
}
class Car extends RoadVehicle {
int getNumberOfWheels() {
return 4;
}
}
abstract class GenericHandler<T extends Vehicle> {
public abstract void displayData(T t);
}
abstract class RoadVehicleHandler<T extends RoadVehicle>
extends GenericHandler<T> {
public void displayData(T t) {
System.out.println(t.getNumberOfWheels() + " wheels");
specialStuff();
}
abstract void specialStuff();
}
class CarHandler extends RoadVehicleHandler<Car> {
void specialStuff() { /* honk horn */ }
}
class TruckHandler extends RoadVehicleHandler<Truck> {
void specialStuff() { /* flash lights */ }
}