目录
Java 内部类
Java面向对象的设计 - Java 内部类
什么是内部类?
作为包的成员的类被称为顶级类。
一个类可以在另一个类中声明。这种类型的类称为内部类。
如果在另一个类中声明的类被显式或隐式声明为static,它被称为嵌套类,而不是内部类。
包含内部类的类称为封闭类或外部类。
例子
下面的代码声明一个内部类。
class Outer { public class Inner { // Members of the Inner class go here } // Other members of the Outer class go here }
Outer类是一个顶级类。
Inner类是一个内部类。它是外类的成员。
外层类是Inner类的封闭(外部)类。
内部类可以是另一个内部类的封闭类。内部类的嵌套层次没有限制。
内部类的实例只能存在于其封闭类的实例中。
使用内部类的优点
以下是内部类的一些优点。
- 在将使用它们的其他类附近定义类。
- 提供了一个额外的命名空间来管理类结构。
- 一些设计模式使用内部类更容易实现。
- 实现回调机制使用内部类是优雅和方便的。它有助于在Java中实现闭包。
访问局部变量的限制
下面的代码演示了访问局部内部类中的局部变量的规则。
main()方法声明两个局部变量x和y。这两个变量都是最终的。
变量x在被初始化之后从不改变,变量y不能被改变,因为它被声明为final。
public class Main { public static void main(String... args) { int x = 1; final int y = 2; class LocalInner { void print() { System.out.println("x = " + x); System.out.println("y = " + y); } } /* * Uncomment the following statement will make the variable x no longer * an effectively final variable and the LocalIneer class will not compile. */ // x = 100; LocalInner li = new LocalInner(); li.print(); } }
上面的代码生成以下结果。
内部类和继承
内部类可以继承另一个内部类,顶级类或其封闭类。
class A { public class B { } public class C extends B { } public class D extends A { } } class E extends A { public class F extends B { } }
内部类中没有静态成员
Java中的关键字static使一个构造成为一个顶层结构。
因此,我们不能为内部类声明任何静态成员(字段,方法或初始化器)。
允许在内部类中有作为编译时常量的静态字段。
class A { public class B { public final static int DAYS_IN_A_WEEK = 7; // OK public final String str = new String("Hello"); } }
生成的内部类的类文件
每个内部类都被编译成一个单独的类文件。
成员内部类和静态内部类的类文件名格式如下:
<outer-class-name>$<member-or-static-inner-class-name>
局部内部类的类文件名的格式如下:
<outer-class-name>$<a-number><local-inner-class-name>
匿名类的类文件名的格式如下:
<outer-class-name>$<a-number>
类文件名中的<a-number>是从1开始顺序生成的数字,以避免任何名称冲突。
静态上下文中的内类
我们可以在静态上下文中定义一个内部类,例如静态方法或静态初始化器。
所有静态字段成员都可以访问这样的内部类。
class Outer { static int k = 1; int m = 2; public static void staticMethod() { // Class Inner is defined in a static context class Inner { int j = k; // OK. Referencing static field k // int n = m; // An error. Referencing non-static field m } } }
Java 内部类类型
Java面向对象设计 - Java内部类类型
您可以在类中的任何位置定义内部类,您可以在其中编写Java语句。
有三种类型的内部类。内部类的类型取决于位置和声明的方式。
- 成员内部类
- 局部内部类
- 匿名内部类
成员内部类
成员内部类在类中声明的方式与声明成员字段或成员方法相同。
它可以声明为public,private,protected或package-level。
成员内部类的实例可以仅存在于其封闭类的实例内。
以下代码创建了一个成员内部类。
class Car { private int year; // A member inner class named Tire public class Tire { private double radius; public Tire(double radius) { this.radius = radius; } public double getRadius() { return radius; } } // Member inner class declaration ends here // A constructor for the Car class public Car(int year) { this.year = year; } public int getYear() { return year; } }
局部内部类
一个局部内部类在块中声明。其范围仅限于声明它的块。
由于其范围限于其封闭块,因此其声明不能使用任何访问修饰符,例如public,private或protected。
通常,在方法内定义局部内部类。但是,它也可以在静态初始化器,非静态初始化器和构造器中定义。
下面的代码显示了一个局部内部类的例子。
import java.util.ArrayList; import java.util.Iterator; public class Main { public static void main(String[] args) { StringList tl = new StringList(); tl.addTitle("A"); tl.addTitle("B"); Iterator iterator = tl.titleIterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } } class StringList { private ArrayList<String> titleList = new ArrayList<>(); public void addTitle(String title) { titleList.add(title); } public void removeTitle(String title) { titleList.remove(title); } public Iterator<String> titleIterator() { // A local inner class - TitleIterator class TitleIterator implements Iterator<String> { int count = 0; @Override public boolean hasNext() { return (count < titleList.size()); } @Override public String next() { return titleList.get(count++); } } TitleIterator titleIterator = new TitleIterator(); return titleIterator; } }
上面的代码生成以下结果。
例子
下面的代码有一个局部内部类继承自另一个公共类。
import java.util.Random; abstract class IntGenerator { public abstract int getValue() ; } class LocalGen { public IntGenerator getRandomInteger() { class RandomIntegerLocal extends IntGenerator { @Override public int getValue() { Random rand = new Random(); long n1 = rand.nextInt(); long n2 = rand.nextInt(); int value = (int) ((n1 + n2) / 2); return value; } } return new RandomIntegerLocal(); } // End of getRandomInteger() method } public class Main { public static void main(String[] args) { LocalGen local = new LocalGen(); IntGenerator rLocal = local.getRandomInteger(); System.out.println(rLocal.getValue()); System.out.println(rLocal.getValue()); } }
上面的代码生成以下结果。
匿名内部类
匿名内部类没有名称。因为它没有名称,它不能有一个构造函数。
匿名类是一次性类。您定义一个匿名类并同时创建它的对象。
创建匿名类及其对象的一般语法如下:
new Interface() { // Anonymous class body goes here }
和
new Superclass(<argument-list-for-a-superclass-constructor>) { // Anonymous class body goes here }
new运算符用于创建匿名类的实例。
它后面是现有的接口名称或现有的类名称。
接口名称或类名称不是新创建的匿名类的名称。
如果使用接口名称,则匿名类实现接口。
如果使用类名,则匿名类继承自类。
仅当新运算符后面跟有类名时,才使用<argument-list>。如果新运算符后跟接口名称,则它为空。
如果<argument-list>存在,它包含要调用的现有类的构造函数的实际参数列表。
匿名类主体像往常一样在大括号中。
匿名类主体应该简短,以便更好的可读性。
下面的代码包含一个简单的匿名类,它在标准输出上打印一条消息。
public class Main { public static void main(String[] args) { new Object() { // An instance initializer { System.out.println("Hello from an anonymous class."); } }; // A semi-colon is necessary to end the statement } }
上面的代码生成以下结果。
例2
以下代码使用匿名类来创建Iterator。
import java.util.ArrayList; import java.util.Iterator; public class Main { private ArrayList<String> titleList = new ArrayList<>(); public void addTitle(String title) { titleList.add(title); } public void removeTitle(String title) { titleList.remove(title); } public Iterator<String> titleIterator() { // An anonymous class Iterator<String> iterator = new Iterator<String>() { int count = 0; @Override public boolean hasNext() { return (count < titleList.size()); } @Override public String next() { return titleList.get(count++); } }; // Anonymous inner class ends here return iterator; } }
Java 静态对象类
Java面向对象设计 - Java静态内部类
静态成员类不是内部类
在另一个类的主体中定义的成员类可以声明为静态。
例子
以下代码声明了顶级类A和静态成员类B:
class A { // Static member class public static class B { // Body for class B goes here } }
注意
静态成员类不是内部类。它被认为是一个顶级类。
静态成员类也称为嵌套顶级类。
A类的实例和B类的实例可以独立存在,因为它们都是顶级类。
静态成员类可以声明为public,protected,package-level或private,以限制其在其封闭类之外的可访问性。
使用静态成员类有两个好处:
- 静态成员类可以访问其包含类的静态成员,包括私有静态成员。
- 一个包通过提供一个命名空间,就像一个顶级类的容器。具有静态成员类的顶级类提供了额外的命名空间层。
静态成员类是其封闭顶级类的直接成员,而不是包的成员。
静态成员类的对象的创建方式与使用new运算符创建顶级类的对象的方式相同。要创建一个B类的对象,你写
A.B bReference = new A.B();
由于类B的简单名称在类A中的范围内,因此我们可以使用其简单名称在类A中创建其对象
B bReference2 = new B(); // This statement appears inside class A code
我们还可以通过导入com.java2s.innerclasses.A.B类,使用A类之外的简单名称B.
例2
下面的代码显示了如何使用静态内部类。
public class Main { public static void main(String[] args) { Car.Tire m = new Car.Tire(17); Car.Tire m2 = new Car.Tire(19); Car.Keyboard k = new Car.Keyboard(122); Car.Keyboard k1 = new Car.Keyboard(142); System.out.println(m); System.out.println(m2); System.out.println(k); System.out.println(k1); } } class Car { // Static member class - Monitor public static class Tire { private int size; public Tire(int size) { this.size = size; } public String toString() { return "Monitor - Size:" + this.size + " inch"; } } // Static member class - Keyboard public static class Keyboard { private int keys; public Keyboard(int keys) { this.keys = keys; } public String toString() { return "Keyboard - Keys:" + this.keys; } } }
上面的代码生成以下结果。
Java 内部类对象
Java面向对象设计 - Java内部类对象
局部内部类的对象是使用块中的新运算符创建的,它声明了类。
在声明类的同时创建一个匿名类的对象。
静态成员类是另一种类型的顶级类。
您可以按照创建顶级类的对象的方式创建静态成员类的对象。
成员内部类的实例始终存在于其封闭类的实例中。
语法
创建成员内部类的实例的一般语法如下:
OuterClassReference.new MemberInnerClassConstructor()
OuterClassReference是包围类的引用,后跟一个后跟新运算符的点。
例子
成员内部类的构造函数调用遵循new运算符。
class Outer { public class Inner { } }
要创建内部成员内部类的实例,必须首先创建其封闭类Outer的实例。
Outer out = new Outer();
现在,您需要在out参考变量上使用new运算符来创建Inner类的对象。
out.new Inner();
为了将内部成员内部类的实例的引用存储在引用变量中,我们可以写下面的语句:
Outer.Inner in = out.new Inner();
以下代码显示了如何创建成员内部类的对象
public class Main { public static void main(String[] args) { Car c = new Car(); Car.Tire t = c.new Tire(9); } } class Car { public class Tire { private int size; public Tire(int size) { this.size = size; } public String toString() { return "Monitor - Size:" + this.size + " inch"; } } }
Java 内部类成员
Java面向对象设计 - Java内部类成员
内部类可以访问其所有实例成员,实例字段和其封闭类的实例方法。
class Outer { private int value = 2014; public class Inner { public void printValue() { System.out.println("Inner: Value = " + value); } } // Inner class ends here public void printValue() { System.out.println("Outer: Value = " + value); } public void setValue(int newValue) { this.value = newValue; } } public class Main { public static void main(String[] args) { Outer out = new Outer(); Outer.Inner in = out.new Inner(); out.printValue(); in.printValue(); out.setValue(2015); out.printValue(); in.printValue(); } }
上面的代码生成以下结果。
例子
以下代码显示如何访问内部类的内部变量。
public class Main { public static void main(String[] args) { Outer out = new Outer(); Outer.Inner in = out.new Inner(); out.printValue(); in.printValue(); out.setValue(3); out.printValue(); in.printValue(); } } class Outer { private int value = 1; public class Inner { private int value = 2; public void printValue() { System.out.println("Inner: Value = " + value); } } // Inner class ends here public void printValue() { System.out.println("Outer: Value = " + value); } public void setValue(int newValue) { this.value = newValue; } }
上面的代码生成以下结果。
在内部类中使用关键字this
以下代码显示如何在内部类中使用关键字this。
class Outer { private int value = 1; class QualifiedThis { private int value = 2; public void printValue() { System.out.println("value=" + value); System.out.println("this.value=" + this.value); System.out.println("QualifiedThis.this.value=" + QualifiedThis.this.value); } public void printHiddenValue() { int value = 2; System.out.println("value=" + value); System.out.println("this.value=" + this.value); System.out.println("QualifiedThis.this.value=" + QualifiedThis.this.value); } } public void printValue() { System.out.println("value=" + value); System.out.println("this.value=" + this.value); } } public class Main { public static void main(String[] args) { Outer outer = new Outer(); Outer.QualifiedThis qt = outer.new QualifiedThis(); System.out.println("printValue():"); qt.printValue(); System.out.println("printHiddenValue():"); qt.printHiddenValue(); outer.printValue(); } }
上面的代码生成以下结果。
隐藏变量
如果实例变量名称被隐藏,您必须使用关键字this或类名称以及关键字this限定其名称。
class TopLevelOuter { private int v1 = 100; // Here, only v1 is in scope public class InnerLevelOne { private int v2 = 200; // Here, only v1 and v2 are in scope public class InnerLevelTwo { private int v3 = 300; // Here, only v1, v2, and v3 are in scope public class InnerLevelThree { private int v4 = 400; // Here, all v1, v2, v3, and v4 are in scope } } } }
从外部类引用变量
以下代码显示如何从外部类引用变量。
public class Test{ private int value = 1; public class Inner { private int value = 2; public void printValue() { System.out.println("Inner: Value = " + value); System.out.println("Outer: Value = " + Test.this.value); } } // Inner class ends here public void printValue() { System.out.println("\nOuter - printValue()..."); System.out.println("Outer: Value = " + value); } public void setValue(int newValue) { System.out.println("\nSetting Outer"s value to " + newValue); this.value = newValue; } }
Java 继承
Java面向对象设计 - Java继承
子类可以从超类继承。超类也称为基类或父类。子类也称为派生类或子类。
从另一个类继承一个类非常简单。我们在子类的类声明中使用关键字extends,后跟超类名称。
Java不支持多重继承的实现。
Java中的类不能有多个超类。
语法
一般的语法是
<class modifiers>class <SubclassName> extends <SuperclassName> { // Code for the Subclass }
例子
以下代码显示如何使用从Employee类创建Manager类。
class Employee { private String name = "Unknown"; public void setName(String name) { this.name = name; } public String getName() { return name; } } class Manager extends Employee { } public class Main { public static void main(String[] args) { // Create an object of the Manager class Manager mgr = new Manager(); // Set the name of the manager mgr.setName("Tom"); // Get the name of the manager String mgrName = mgr.getName(); // Display the manager name System.out.println("Manager Name: " + mgrName); } }
上面的代码生成以下结果。
注意
我们没有为Manager类编写任何代码,它的工作原理与Employee类相同,因为它继承自Employee类。
您可以使用Manager类的构造函数创建一个管理器对象。
Manager mgr = new Manager();
创建管理器对象后,Manager对象的工作方式与Employee对象相同。
我们对manager对象使用了setName()和getName()方法。
mgr.setName("Tom"); String mgrName = mgr.getName();
Manager类不声明setName()和getName()方法。Manager类“扩展Employee"。
当一个类从另一个类继承时,它继承它的超类成员,实例变量,方法等。
对象父类
对象类是默认超类。
所有类都隐式继承自Object类。因此所有类的对象都可以使用Object类的方法。
public class P { }
类P从Object扩展,即使我们没有指定父类。
Object类声明了hashCode()和toString()方法。因为Employee类是Object类的一个子类,它可以使用这些方法。
Employee emp = new Employee(); int hc = emp.hashCode(); String str = emp.toString();
Employee类不使用extends子句指定其超类。这意味着它继承自Object类。
Object类声明了hashCode()和toString()方法。因为Employee类是Object类的一个子类,它可以使用这些方法。
向上转换和向下转换
现实世界中的“is-a”关系在软件中转化为继承类层次结构。
例如,Manager是特定类型的Employee。 Employee是一种特定类型的Object。
当您在继承层次结构中向上移动时,将从特定类型移动到更一般的类型。
从子类到超类的赋值称为上转换,它在Java中始终允许。
class Employee { private String name = "Unknown"; public void setName(String name) { this.name = name; } public String getName() { return name; } } class Manager extends Employee { } public class Main { public static void printName(Employee emp) { String name = emp.getName(); System.out.println(name); } public static void main(String[] args) { Employee emp = new Employee(); emp.setName("Tom"); Manager mgr = new Manager(); mgr.setName("Jack"); // Inheritance of setName() at work // Print names printName(emp); printName(mgr); // Upcasting at work } }
为子类变量分配超类引用称为向下转换。
向下转换与向上转换相反。
在向上转换中,赋值向上移动类层次结构,而在向下转换中,赋值向下移动类层次结构。
我们必须在向下转换中使用类型转换。
Manager mgr = (Manager)emp; // OK. Downcast at work
上面的代码生成以下结果。
instanceof运算符
Java instanceof运算符帮助我们确定引用变量是否有对类或子类的引用。
它需要两个操作数,并计算为布尔值true或false。
它的语法是
<Class Reference Variable> instanceof <Class Name or Interface>
如果<Class Reference Variable>引用类<Class Name>或其任何后代的对象,instanceof返回true。
如果引用变量为null,instanceof总是返回false。
我们应该在向下转换之前使用instanceof运算符。
Manager mgr = new Manager(); Employee emp = mgr; if (emp instanceof Manager) { // downcast will succeed mgr = (Manager)emp; }else { // emp is not a Manager type }
禁用继承
我们可以通过声明类final来禁用子类。
最终的类不能被子类化。
下面的代码声明了一个名为MyClass的最终类:
public final class MyClass{ }
我们也可以声明一个方法为final。最终方法不能被子类覆盖或隐藏。
public class A { public final void m1() { } public void m2() { } }
Java 方法重写
Java面向对象设计 - Java方法重写
方法重写
重新定义从超类继承的类中的实例方法称为方法重写。
例子
让我们考虑类A和类B的以下声明:
public class A { public void print() { System.out.println("A"); } } public class B extends A { public void print() { System.out.println("B"); } }
类B是类A的子类。类B从其超类继承print()方法并重新定义它。
类B中的print()方法覆盖类A的print()方法。
如果一个类覆盖了一个方法,它会影响覆盖的类及其子类。考虑下面的类C的声明:
public class C extends B { // Inherits B.print() }
类C不声明任何方法。它继承类B中的print()方法。
注意
类总是继承它的直接超类的可用的。
方法必须是实例方法。重写不适用于静态方法。
重写方法必须具有与重写方法相同的名称。
重写方法必须具有与重写方法相同顺序的相同类型的相同数量的参数。
当方法的参数使用通用类型时,考虑通用类型参数的擦除,而不是通用类型本身与其他方法比较。
参数的名称无关紧要。
如果重写方法的返回类型是引用类型,则重写方法的返回类型必须与重写方法的返回类型兼容。
访问级别
重写方法的访问级别必须至少与重写方法的访问级别相同或更宽松。
下表列出了重写方法允许的访问级别
重写方法访问级别 | 允许重写方法访问级别... |
---|---|
public | public |
protected | public, protected |
package-level | public, protected, package-level |
方法可以在其throws子句中包括检查异常的列表。重写方法无法向重写方法中的异常列表添加新的异常。
它可以删除一个或所有异常,或者可以用另一个异常替换异常。
访问重写方法
从子类访问重写的方法。子类可以使用关键字 super
作为限定符来调用超类的重写方法。
class MySuperClass { public void print() { System.out.println("Inside MySuperClass"); } } class MySubClass extends MySuperClass { public void print() { // Call print() method of MySuperClass class super.print(); // Print a message System.out.println("Inside MySubClass.print()"); } public void callOverridenPrint() { // Call print() method of MySuperClass class super.print(); } } public class Main { public static void main(String[] args) { MySubClass aoSub = new MySubClass(); aoSub.print(); aoSub.callOverridenPrint(); } }
上面的代码生成以下结果。
Java 继承和构造函数
Java面向对象设计 - Java继承和构造函数
构造函数不是类的成员,它们不是由子类继承的。
它们用于初始化实例变量。
class CSuper { public CSuper() { System.out.println("Inside CSuper() constructor."); } } class CSub extends CSuper { public CSub() { System.out.println("Inside CSub() constructor."); } } public class Main { public static void main(String[] args) { CSub cs = new CSub(); } }
上面的代码生成以下结果。
例子
下面显示了如何编译器注入一个super()来立即调用父类的无参数构造函数。
class CSuper { public CSuper() { super(); // Injected by the compiler System.out.println("Inside CSuper() constructor."); } } class CSub extends CSuper { public CSub() { super(); // Injected by the compiler System.out.println("Inside CSub() constructor."); } } public class Main { public static void main(String[] args) { CSub cs = new CSub(); } }
上面的代码生成以下结果。
关键字super指的是类的直接父类。
我们可以调用超类构造函数,只使用super关键字作为构造函数中的第一个语句。
无参数构造函数
我们可以将超类的no-args构造函数或任何其他构造函数显式地调用为类的构造函数中的第一个语句。
只有在没有明确添加的情况下,编译器才会注入no-args构造函数调用。
class Employee { private String name = "Unknown"; public Employee(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return name; } } class Manager extends Employee { public Manager(String name) { super(name); } } public class Main { public static void main(String[] args) { Manager mgr = new Manager("Tom"); String name = mgr.getName(); System.out.println("Manager name: " + name); } }
上面的代码生成以下结果。
每个类都必须直接或间接地从其构造函数中调用其超类的构造函数。
如果超类没有no-args构造函数,我们必须显式地调用超类的任何其他构造函数。
Java 继承隐藏
Java面向对象的设计 - Java继承隐藏
方法隐藏
类从其超类继承所有非私有静态方法。
重定义类中继承的静态方法称为方法隐藏。
子类中的重定义静态方法隐藏其超类的静态方法。
在类中重定义非静态方法称为方法覆盖。
关于方法隐藏的重定义方法(名称,访问级别,返回类型和异常)的所有规则与方法覆盖相同。
class MySuper { public static void print() { System.out.println("Inside MySuper.print()"); } } class MySubclass extends MySuper { public static void print() { System.out.println("Inside MySubclass.print()"); } } public class Main { public static void main(String[] args) { MySuper mhSuper = new MySub(); MySubclass mhSub = new MySubclass(); MySuper.print(); MySubclass.print(); ((MySuper) mhSub).print(); mhSuper = mhSub; mhSuper.print(); ((MySubclass) mhSuper).print(); } }
上面的代码生成以下结果。
字段隐藏
类中的字段声明(静态或非静态)在其父类中隐藏具有相同名称的继承字段。
在字段隐藏的情况下,不考虑字段的类型及其访问级别。
字段隐藏仅基于字段名称。
class MySuper { protected int num = 100; protected String name = "Tom"; } class MySub extends MySuper { public void print() { System.out.println("num: " + num); System.out.println("name: " + name); } } class MySub2 extends MySuper { // Hides num field in MySuper class private int num = 200; // Hides name field in MySuper class private String name = "Jack"; public void print() { System.out.println("num: " + num); System.out.println("name: " + name); } } public class Main { public static void main(String[] args) { MySub fhSub = new MySub(); fhSub.print(); MySub2 fhSub2 = new MySub2(); fhSub2.print(); } }
上面的代码生成以下结果。
例子
以下代码显示了如何使用super关键字访问超类的隐藏字段
class MySuper { protected int num = 100; protected String name = "Tom"; } class MySub extends MySuper { // Hides the num field in MySuper class private int num = 200; // Hides the name field in MySuper class private String name = "Jack"; public void print() { System.out.println("num: " + num); System.out.println("super.num: " + super.num); System.out.println("name: " + name); System.out.println("super.name: " + super.name); } } public class Main { public static void main(String[] args) { MySub s = new MySub(); s.print(); } }
上面的代码生成以下结果。
字段隐藏发生在一个类声明一个变量与来自其超类的继承变量具有相同名称的时候。
字段隐藏仅基于字段的名称。
类应该使用关键字super来访问超类的隐藏字段。
类可以使用简单的名称来访问其主体中的重定义字段
Java 抽象类和方法
Java面向对象设计 - Java抽象类和方法
Java可以定义一个类,其对象不能被创建。
它的目的只是表示一个想法,这是其他类的对象共有的。
这样的类称为抽象类。
语法
我们需要在类声明中使用 abstract
关键字来声明一个抽象类。
例如,下面的代码声明一个Shape类的抽象:
public abstract class Shape { }
下面的代码为Shape类添加了一个draw()方法。
public abstract class Shape { public Shape() { } public abstract void draw(); }
抽象类不一定意味着它具有至少一个抽象方法。
如果一个类有一个被声明或继承的抽象方法,它必须被声明为抽象。
抽象方法的声明方式与任何其他方法相同,只是它的主体由分号表示。
例子
下面的Shape类有抽象和非抽象方法。
abstract class Shape { private String name; public Shape() { this.name = "Unknown shape"; } public Shape(String name) { this.name = name; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } // Abstract methods public abstract void draw(); public abstract double getArea(); public abstract double getPerimeter(); }
下面的代码展示了如何创建一个Rectangle类,它继承自Shape类。
class Rectangle extends Shape { private double width; private double height; public Rectangle(double width, double height) { // Set the shape name as "Rectangle" super("Rectangle"); this.width = width; this.height = height; } // Provide an implementation for inherited abstract draw() method public void draw() { System.out.println("Drawing a rectangle..."); } // Provide an implementation for inherited abstract getArea() method public double getArea() { return width * height; } // Provide an implementation for inherited abstract getPerimeter() method public double getPerimeter() { return 2.0 * (width + height); } }
Java 泛型类
Java面向对象设计 - Java泛型类
使用泛型,我们可以在不知道代码操作对象的类型的情况下编写代码。它允许我们创建泛型类,构造函数和方法。
泛型类是使用形式类型参数定义的。
形式类型参数是一个逗号分隔的变量名列表,位于类声明中类名后面的尖括号<>中。
语法
下面的代码声明一个类Wrapper,它接受一个形式类型参数:
public class Wrapper<T> { }
该参数已命名为T.
T是一个类型变量,它可以是Java中的任何引用类型,例如String,Integer,Double等。
当使用Wrapper类时,指定形式类型参数值。
形式参数
采用形式类型参数的类也称为参数化类。
您可以声明Wrapper类的变量,将String类型指定为其形式类型参数的值,如下所示。
这里,String是实际的类型参数。
Wrapper<String> stringWrapper;
Java允许我们使用泛型类而不指定形式类型参数。
用法
这是为了向后兼容。您还可以声明Wrapper<T>类的变量,如下所示:
Wrapper aRawWrapper;
当使用泛型类而不指定实际的类型参数时,它被称为原始类型。上面的声明使用Wrapper <T>类作为原始类型,因为它没有指定T的值。
泛型类的实际类型参数必须是引用类型。
原始类型不允许作为泛型类的实际类型参数。
类可以采用多个正式类型参数。下面的代码声明一个Mapper类,它接受两个形式参数T和R:
public class Mapper<T, R> { }
我们可以声明Mapper <T,R>类的变量如下:
Mapper<String, Integer> mapper;
这里,实际的类型参数是String和Integer。
形式类型参数在类体中可用作类型。
public class Wrapper<T> { private T obj; public Wrapper(T obj) { this.obj = obj; } public T get() { return obj; } public void set(T obj) { this.obj = obj; } }
Wrapper<T>类使用形式类型参数来声明实例变量obj,以声明其构造函数和set()方法的形式参数,以及作为get()方法的返回类型。
您可以通过为构造函数指定实际的类型参数来创建泛型类型的对象,如下所示:
Wrapper<String> w1 = new Wrapper<String>("Hello");
我们可以省略实际的类型参数。在下面的代码中,编译器会将构造函数的实际类型参数推断为String:
Wrapper<String> w1 = new Wrapper<>("Hello");
一旦你声明了泛型类的一个变量,你可以把形式类型参数看作是指定的实际类型参数。
现在,你可以认为,对于w1,Wrapper类的get()方法返回一个String。
String s1= w1.get();
例子
以下代码显示如何使用Wrapper类。
public class Main { public static void main(String[] args) { Wrapper<String> w1 = new Wrapper<>("Hello"); String s1 = w1.get(); System.out.println("s1=" + s1); w1.set("Testing generics"); String s2 = w1.get(); System.out.println("s2=" + s2); w1.set(null); String s3 = w1.get(); System.out.println("s3=" + s3); } } class Wrapper<T> { private T obj; public Wrapper(T obj) { this.obj = obj; } public T get() { return obj; } public void set(T obj) { this.obj = obj; } }
Java 泛型约束
Java面向对象设计 - Java泛型约束
无限通配符
通配符类型由问号表示,如<?> 。
对于通用类型,通配符类型是对象类型用于原始类型。
我们可以将任何已知类型的泛型分配为通配符类型。
这里是示例代码:
// MyBag of String type MyBag<String> stringMyBag = new MyBag<String>("Hi"); // You can assign a MyBag<String> to MyBag<?> type MyBag<?> wildCardMyBag = stringMyBag;
通配符通配类型中的问号(例如,<?>)表示未知类型。
当您使用通配符声明参数化类型作为参数类型时,这意味着它不知道它的类型。
MyBag<?> unknownMyBag = new MyBag<String>("Hello");
上限通配符
我们表示通配符的上限
<? extends T>
这里,T是一种类型。<? extends T>表示任何类型为T或其子类是可接受的。
例如,上限可以是数字类型。
如果我们通过任何其他类型,该类型是数字类型的子类,很好。但是,不是Number类型或其子类型的任何东西都应该在编译时被拒绝。
使用上限作为数字,我们可以将方法定义为
class MyBag<T> { private T ref; public MyBag(T ref) { this.ref = ref; } public T get() { return ref; } public void set(T a) { this.ref = a; } } public class Main { public static double sum(MyBag<? extends Number> n1, MyBag<? extends Number> n2) { Number num1 = n1.get(); Number num2 = n2.get(); double sum = num1.doubleValue() + num2.doubleValue(); return sum; } }
不管你为n1和n2传递什么,它们将始终与Number的赋值兼容,因为编译器确保传递给sum()方法的参数遵循其声明中指定的规则 <? extends Number>。
下限通配符
指定下限通配符与指定上限通配符相反。
使用下限通配符的语法是<? super T>,这意味着“任何是T的超类型”。
class MyBag<T> { private T ref; public MyBag(T ref) { this.ref = ref; } public T get() { return ref; } public void set(T a) { this.ref = a; } } public class Main { public static <T> void copy(MyBag<T> source, MyBag<? super T> dest) { T value = source.get(); dest.set(value); } }
Java 泛型方法和构造函数
Java面向对象设计 - Java泛型方法和构造函数
泛型方法
我们可以在方法声明中定义类型参数,它们在方法的返回类型之前的尖括号中指定。
包含泛型方法声明的类型不必是泛型类型。
我们可以在非静态方法声明中使用为泛型类型指定的类型参数。
例子
以下代码显示如何为方法m1()定义新的类型参数V.
新类型参数V强制方法m1()的第一个和第二个参数为相同类型。
第三个参数必须是相同的类型T,这是类实例化的类型。
class MyBag<T> { private T ref; public MyBag(T ref) { this.ref = ref; } public T get() { return ref; } public void set(T a) { this.ref = a; } } class Test<T> { public <V> void m1(MyBag<V> a, MyBag<V> b, T c) { } }
使用泛型方法
要传递方法的形式类型参数的实际类型参数,我们必须在方法调用中的点和方法名之间的尖括号<>中指定它。
class MyBag<T> { private T ref; public MyBag(T ref) { this.ref = ref; } public T get() { return ref; } public void set(T a) { this.ref = a; } } class Test<T> { public <V> void m1(MyBag<V> a, MyBag<V> b, T c) { } } public class Main { public static void main(String[] argv) { Test<String> t = new Test<String>(); MyBag<Integer> iw1 = new MyBag<Integer>(new Integer(201)); MyBag<Integer> iw2 = new MyBag<Integer>(new Integer(202)); // Specify that Integer is the actual type for the type parameter for m1() t.<Integer>m1(iw1, iw2, "hello"); t.m1(iw1, iw2, "hello"); } }
例2
以下代码显示了如何声明泛型静态方法。
我们不能在静态方法内引用包含类的类型参数。
静态方法只能引用它自己声明的类型参数。
以下静态通用类型定义了类型参数T,用于约束参数source和dest的类型。
class MyBag<T> { private T ref; public MyBag(T ref) { this.ref = ref; } public T get() { return ref; } public void set(T a) { this.ref = a; } } public class Main { public static <T> void copy(MyBag<T> source, MyBag<? super T> dest) { T value = source.get(); dest.set(value); } public static void main(String[] argv) { } }
要为静态方法调用指定实际的类型参数,我们可以这样做:
Main.<Integer>copy(iw1, iw2);
泛型构造函数
我们可以为构造函数定义类型参数。
下面的代码定义了类Test的构造函数的类型参数U.
它放置一个约束,即构造函数的类型参数U必须是相同的,或者它的类类型参数T的实际类型的子类型。
public class Test<T> { public <U extends T> Test(U k) { } }
要为构造函数指定实际的类型参数值,请在新运算符和构造函数名称之间的尖括号中指定它,如以下代码段所示:
class Test<T> { public <U extends T> Test(U k) { } } public class Main { public static void main(String[] argv) { // Specify the actual type parameter for the constructor as Double Test<Number> t1 = new<Double> Test<Number>(new Double(1.9)); // Let the compiler figure out, Integer is // the actual type parameter for the constructor Test<Number> t2 = new Test<Number>(new Integer(1)); } }
泛型对象创建中的类型推断
Java 7在泛型类型的对象创建表达式中增加了对类型推断的有限支持。
对于以下语句:
List<String> list = new ArrayList<String>();
在Java 7中,可以在上面的语句中指定空尖括号,称为菱形操作符或简单的菱形<>作为ArrayList的类型参数。
List<String> list = new ArrayList<>();
如果我们不在对象创建表达式中为泛型类型指定类型参数,那么类型是原始类型,编译器生成未检查的警告。
例如,以下语句将编译未选中的警告:
List<String> list = new ArrayList(); // Generates an unchecked warning
我们不能创建泛型的异常类。并且没有泛型的匿名类。