Когда мы указываем универсальный параметр у обобщений, то по умолчанию он может представлять любой тип. Однако иногда необходимо, чтобы параметр соответствовал только некоторому ограниченному набору типов. В этом случае применяются ограничения, которые позволяют указать базовый класс, которому должен соответствовать параметр.
Для установки ограничения после универсального параметра ставится слово extends, после которого указывается базовый класс ограничения:
class Account{ } class Transaction<T extends Account>{ }
К примеру, в данном случае для параметра T в Transaction ограничением является класс Account. То есть на место параметра T мы можем передать либо класс Account, либо один из его классов-наследников.
Например, рассмотрим следующую программу:
public class Program{ public static void main(String[] args) { Account acc1 = new Account("1876", 4500); Account acc2 = new Account("3476", 1500); Transaction<Account> tran1 = new Transaction<Account>(acc1,acc2, 4000); tran1.execute(); tran1 = new Transaction<Account>(acc1,acc2, 4000); tran1.execute(); } } class Transaction<T extends Account>{ private T from; // с какого счета перевод private T to; // на какой счет перевод private int sum; // сумма перевода Transaction(T from, T to, int sum){ this.from = from; this.to = to; this.sum = sum; } public void execute(){ if (from.getSum() > sum) { from.setSum(from.getSum() - sum); to.setSum(to.getSum() + sum); System.out.printf("Account %s: %d \nAccount %s: %d \n", from.getId(), from.getSum(),to.getId(), to.getSum()); } else{ System.out.printf("Operation is invalid"); } } } class Account{ private String id; private int sum; Account(String id, int sum){ this.id = id; this.sum = sum; } public String getId() { return id; } public int getSum() { return sum; } public void setSum(int sum) { this.sum = sum; } }
В данном случае класс Transaction, который представляет операцию перевода средств между двумя счетами, типизирован параметром T, у которого в качестве ограничения установлен класс Account. При создании объекта Transaction в его конструктор передаются два объекта Account - два счета, между которыми надо осуществить перевод, и сумма перевода.
При этом важно понимать, что поскольку мы установили подобное ограничение, то компилятор будет распознавать объекты типа T как объекты типа Account. И в этом случае мы можем вызывать у объектов типа T методы класса Account. И мы бы не смогли бы это сделать, если бы мы не задали подобного ограничения:
class Transaction<T>{ // остальное содержимое }
В этом случае была бы ошибка.
В качестве ограничений могут выступать и другие обобщения, которые сами могут иметь ограничения:
public class Program{ public static void main(String[] args) { Account<String> acc1 = new Account<String>("1876", 4500); Account<String> acc2 = new Account<String>("3476", 1500); Transaction<Account<String>> tran1 = new Transaction<Account<String>>(acc1,acc2, 4000); tran1.execute(); tran1 = new Transaction<Account<String>>(acc1,acc2, 4000); tran1.execute(); } } class Transaction<T extends Account<String>>{ private T from; // с какого счета перевод private T to; // на какой счет перевод private int sum; // сумма перевода Transaction(T from, T to, int sum){ this.from = from; this.to = to; this.sum = sum; } public void execute(){ if (from.getSum() > sum) { from.setSum(from.getSum() - sum); to.setSum(to.getSum() + sum); System.out.printf("Account %s: %d \nAccount %s: %d \n", from.getId(), from.getSum(),to.getId(), to.getSum()); } else{ System.out.printf("Operation is invalid"); } } } class Account<T>{ private T id; private int sum; Account(T id, int sum){ this.id = id; this.sum = sum; } public T getId() { return id; } public int getSum() { return sum; } public void setSum(int sum) { this.sum = sum; } }
В данном случае ограничением для Transaction является тип Account, который типизирован типом String.
В качестве ограничений могут выступать также интерфейсы. В этом случае передаваемый на место универсального параметра тип должен реализовать данный интерфейс:
public class Program{ public static void main(String[] args) { Account acc1 = new Account("1235rwr", 5000); Account acc2 = new Account("2373", 4300); Transaction<Account> tran1 = new Transaction<Account>(acc1, acc2, 1560); tran1.execute(); } } interface Accountable{ String getId(); int getSum(); void setSum(int sum); } class Account implements Accountable{ private String id; private int sum; Account(String id, int sum){ this.id = id; this.sum = sum; } public String getId() { return id; } public int getSum() { return sum; } public void setSum(int sum) { this.sum = sum; } } class Transaction<T extends Accountable>{ private T from; // с какого счета перевод private T to; // на какой счет перевод private int sum; // сумма перевода Transaction(T from, T to, int sum){ this.from = from; this.to = to; this.sum = sum; } public void execute(){ if (from.getSum() > sum) { from.setSum(from.getSum() - sum); to.setSum(to.getSum() + sum); System.out.printf("Account %s: %d \nAccount %s: %d \n", from.getId(), from.getSum(),to.getId(), to.getSum()); } else{ System.out.printf("Operation is invalid"); } } }
Также можно установить сразу несколько ограничений. Например, пусть класс Transaction может работать только с объектами, которые одновременно реализуют интерфейс Accountable и являются наследниками класса Person:
class Person{} interface Accountable{} class Transaction<T extends Person & Accountable>{}