多态性是指同一个名字的若干个方法,有不同的实现(即方法体中的代码不一样)。
多态的两种表现形式:
方法重载overloading(静态多态性)
方法覆盖overriding (动态多态性)
在Java中每个方法都有签名,就是方法名以及参数的个数和类型。如果两个方法参数的个数或类型不同,它们可以具有相同的名字,这种现象叫做重载。当调用方法时,编译器通过比较自变量参数的个数和类型来查找匹配得最好的方法。
签名不包括返回类型或者抛出异常的列表,所以不能通过这些来重载方法。
对于重载的多个同名方法,在编译时能够确定执行同名方法中的哪一个,故也称为编译时多态性。
代码:
class Father{
int x=5;
int addX(int b){
return x+b;
}
}
class Son extends Father{
int addX(int b,int c){
return x+b+c;
}
String addX(String s){
return x+s;
}
}
public class Test{
public static void main(String args[]){
Son a=new Son();
System.out.println(a.addX(2));
System.out.println(a.addX(2,3));
System.out.println(a.addX("2"));
}
}
result:
7
10
52
在子类和超类中有同名的方法(参数也相同),子类中的方法覆盖超类的方法。
如果超类和子类有同名且参数相同的方法,那么超类的对象调用超类的方法,子类的对象调用子类的方法。
通过覆盖可以使同名的方法在不同层次的类中有不同的实现。
注意:子类中重写的方法和父类中被重写的方法要具有相同的名字,相同的参数表和相同的返回类型,只是函数体不同。
Code:
class Father{
int x=8;
int addX(int b){
return x+b;
}
}
class Son extends Father{
int addX(int c){
return x+c+10;
}
}
public class Test{
public static void main(String args[]){
Son a=new Son();
System.out.println(a.addX(2));
}
}
//调用被覆盖的父类方法:
class Son extends Father{
int addX(int c){
return super.addX(c);
}
}
子类方法的名称、参数签名和返回类型必须与其父类的方法的名称、参数签名和返回类型一致。
子类方法不能缩小父类方法的访问权限。
子类方法不能抛出比父类方法更多的异常。
方法覆盖只存在于子类和父类(包括直接父类和间接父类)之间。在同一个类中方法只能被重载,不能被覆盖。
父类的静态方法不能被子类覆盖为非静态的方法,反之亦然。
子类可以定义与父类的静态方法同名的静态方法,以便在子类中隐藏父类的静态方法。
父类的私有方法不能被覆盖。
父类的抽象方法可以被子类覆盖:子类实现父类的方法或重新声明父类的抽象方法。
Code:
class Father{
int x=8;
int addX(int b){
return x+b;
}
}
class Son extends Father{
@Override
int addX(int c){
return x+c+10;
}
}
Java注解(annotation),Override确保子类覆盖(重写)超类方法。
一个特定的变量可以用于引用不同类型的对象,并且自动调用该变量引用的特定类型对象的方法,这样就使得一个方法的调用根据该调用所用到的不同对象类型而响应不同的操作。如果在编译时不能确定,只能在运行时才能确定执行多个同名方法中的哪一个,则称为运行时多态性(动态多态性)。
Code:
class Student{
String name;
public Student(String n){
name=n;
}
public String toString(){
return name;
}
}
class Undergraduate extends Student{
String teacher;
public Undergraduate(String n,String t){
super(n);
teacher=t;
}
public String toString(){
return name+"的班主任是"+teacher;
}
}
class Postgraduate extends Student{
String director;
public Postgraduate(String n,String d){
super(n);
director=d;
}
public String toString(){
return name+"的导师是"+director;
}
}
public class Test{
public static void print(Student s){
System.out.println ("姓名:"+s.name);
}
public static void main (String[] args) {
Student a=new Undergraduate("小张","张老师");
Student b=new Postgraduate("小王","李教授");
Undergraduate c=new Undergraduate("小赵","钱老师");
System.out.println (a.toString());
System.out.println (b.toString());
print(c);
}
}
result:
小张的班主任是张老师
小王的导师是李教授
姓名:小赵
派生类对象的方法调用必须通过一个基类类型的变量进行。
调用的方法必须在派生类中被定义。
调用的方法也必须被声明为基类的一个成员。
基类和派生类中对应的方法的签名必须相同。
派生类的方法的访问说明符不能比基类有更多的限制。
Abstract关键字可修饰类和方法,被修饰的类称为抽象类,被修饰的方法称为抽象方法。
抽象类中的方法定义可以没有方法体,只有方法声明。抽象类没有对象,也就是说,一个抽象类不能通过new操作符直接实例化。
被abstract修饰的方法在非抽象的子类中必须有具体的实现。
抽象类是专门设计来让子类继承的类。
抽象类提供一个类型的部分实现,可以有实例变量,构造方法,抽象方法和具体方法。
抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式设计。
抽象类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能,但这个产品依然不能当成最终产品,必须有更进一步的完善,这种完善可能有几种不同方式。
abstract class 类名{
成员变量;
方法( ){ 方法体 };//定义一般方法
abstract 方法( ); //定义抽象方法
}
对于成员方法,不能同时用static和abstract说明。对于类,不能同时用final和abstract说明。
有抽象方法的类一定是抽象类,抽象类不一定有抽象方法。
代码:
abstract class Shape{
protected String name;
public Shape(String name){
this.name=name;
}
abstract protected double getArea();
}
class Rectangle extends Shape{
double width,length;
public Rectangle(String name, double width, double length){
super(name);
this.width=width;
this.length=length;
}
public double getArea(){
return width*length;
}
}
public class Test {
public static void main(String args[]) {
Rectangle r=new Rectangle("矩形", 10.0, 23.5);
System.out.println(r.getArea());
}
}
"接口"是抽象类的概念,实际上是一组相关常量和(或)抽象方法的集合,并且在大多数情况下,它只包含方法。
接口不会定义方法是什么,而只定义方法的形式,即名称、参数和返回类型,因此接口中的方法都是没有方法体的抽象方法。
接口中只能定义 static final 域。
接口定义的仅仅是实现某一特定功能的一组方法的对外接口和规范,而并没有真正地实现这个功能。
接口的功能实现是在"继承"了这个接口的各个类中完成的,由这些类来具体定义接口中所有抽象方法的方法体。
通常把对接口的"继承"称为"实现"。
public interface 接口名 [extends 父接口名列表] {
// 常量域声明
[public static final] 域类型 域名 = 常量值;
// 抽象方法声明
[public abstract] 返回值类型 方法名( 参数列表 ) ;
}
一个类要实现接口时,要注意下列问题:
在类的声明部分,用implements关键字声明该类将实现哪些接口。
如果实现了某个接口的类不是abstract的抽象类,则在类的定义部分必须实现指定接口的所有抽象方法,即为所有抽象方法定义方法体。
如果实现了某个接口的类是abstract的抽象类,则它可以不实现指定接口的所有抽象方法。
接口的抽象方法的访问控制符为public,所以类在实现方法时,必须显式地使用public。
实现接口的类要实现接口的全部方法。如果不需要某个方法,也要定义成一个空方法体的方法。
如:public void 方法名() { }
例子:
interface CalArea {
double PI = 3.14;
double getArea();
}
class Circle implements CalArea {
double r;
public Circle(double r) {
this.r = r;
}
public double getArea() {
// PI=3.14159; 错误,pi隐含为final和static的
return PI * r * r;
}
}
class Square implements CalArea {
double s;
public Square(double s) {
this.s = s;
}
public double getArea() {
return s * s;
}
}
public class Test {
public static void main(String args[]) {
Circle a = new Circle(3);
Square b = new Square(3);
System.out.println(a.getArea());
System.out.println(b.getArea());
}
}
例子:
interface Remote {
void open();
void close();
}
class Tv implements Remote {
public void open() {
System.out.println("打开电视。");
}
public void close() {
System.out.println("关闭电视。");
}
}
class Vcd implements Remote {
public void open() {
System.out.println("打开VCD。");
}
public void close() {
System.out.println("关闭VCD。");
}
}
public class Test {
public static void main(String args[]) {
Remote r = new Tv();
r.open();
r.close();
r = new Vcd();
r.open();
}
}
接口作为系统与外界交互的窗口,体现的是一种规范。
对接口的实现者而言,接口规定了实现者必须向外提供哪些服务(以方法的形式来提供);
对于接口的调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务(就是如何来调用方法)。
当在一个程序中使用接口时,接口是多个模块间的耦合标准;当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。
接口可以多重继承,抽象类不可以。
抽象类内部可以有实现的方法,接口则没有实现的方法。
接口与实现它的类不构成类的继承体系,即接口不是类体系的一部分。因此,不相关的类也可以实现相同的接口。而抽象类是属于一个类的继承体系,并且一般位于类体系的顶层。
接口的优势:通过实现多个接口实现多重继承,能够抽象出不相关类之间的相似性。
创建类体系的基类时,若不定义任何变量并无需给出任何方法的完整定义,则定义为接口;必须使用方法定义或变量时,考虑用抽象类。
大部分时候,我们把类定义成一个独立的程序单元。在某些情况下,我们把一个类放在另一个类的内部定义,这个定义在其他类内部的类就被称为内部类,包含内部类的类被称为外部类。
内部类有如下作用:
①内部类提供了更好的封装。
②内部类方法可以访问外部类私有数据。
内部类的类名只用于定义它的类或语句块之内,在外部引用它时必须给出带有外部类名的完整名称,并且内部类的名字不允许与外部包类的名字相同。
内部类可以是抽象类或接口,若是接口,则可以由其它内部类实现
按照内部类是否含有显示的类名,可将内部类分为:
实名内部类
局部内部类
匿名内部类
内部类一般用于定义与其外部的对象有很强关联的对象,并且两者之间通常有紧密的联系。
当定义内部类的时候,它与其他类成员一样,会成为外部类的一个成员;也可以具有访问属性,并且外部类对它的访问性也是同样取决于这个属性值。
格式:
[类修饰词表] class 类名 [extends 父类名] [implements 接口名列表]{
类体
}
实名内部类的封装性增加了保护模式和私有模式,即实名内部类的修饰词可以是protected或private
实名内部类的修饰词可以是static,称为静态实名内部类
没有static修饰的内部类,称为不具有静态属性的实名内部类,它的成员域若有静态属性,则必须有final属性,但不能有静态属性的方法
代码:
public class Test {
JFrame frame=new JFrame("测试窗口"); //窗口
JPanel panel=new JPanel(); //面板
private JLabel label=new JLabel("没有操作"); //标签
private JButton button=new JButton("确定"); //按钮
private class ButtonListerner implements ActionListener{ //监听器类
@Override
public void actionPerformed(ActionEvent e) {
label.setText("点击了按钮");
}
}
public Test(){
panel.add(label); //将标签加到面板中
panel.add(button); //将按钮加到面板中
frame.add(panel); //将面板加到窗口中
button.addActionListener(new ButtonListerner());
frame.setBounds(200, 100, 400, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String args[]) {
new Test();
}
}
如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。
1、在局部内部类前不能用public、protected和private,只允许是abstract、final或不加。
2、可以定义与外部类同名的变量。
3、不可以定义静态变量和方法。
4、可以访问外部类的局部变量,但是变量必须是final的。
5、可以访问外部类的所有成员。
代码:
class Outter {
String str="abc";
int a=10;
public void sample1() {
final int b=12;
String str3="123";
class Inner { // 不能用public、protected和private修饰类
int a=12;
// static String str3="aaa"; 内部类中不能定义STATIC变量。
public void sample2() {
System.out.println(b); //局部常量b
System.out.println(str); //外部类成员变量str
System.out.println(Outter.this.a);//外部类成员变量a
System.out.println(a); //内部类成员变量a
// 不能引用另一方法中定义的内部类中非终态变量 str3
// System.out.println("外部类成员变量str3的值"+str3);
}
}
new Inner().sample2();
}
}
public class Test {
public static void main(String[] args) {
Outter outter=new Outter();
outter.sample1();
}
}
有时仅仅为了在程序中定义一个对象而定义类,而且这个对象的唯一作用就是将它作为实参传递给方法。在这种情况下,只要类扩展已存在的类或者实现接口,就可以选择将其作为匿名类。
匿名类没有名字,类的定义和一个实例的创建同时进行,也就是说定义实际上是写在new的代码中,而不是拥有class关键字;匿名类不能有修饰符,也不能定义构造方法。
new 父类名(父类型的构造方法的调用参数列表){
类体
}
代码1:
interface A {
void print();
}
public class Test {
public static void main(String args[]) {
A a=new A() {
public void print() {
System.out.println("匿名类");
}
};
a.print();
}
}
public class Test {
JFrame frame=new JFrame("测试窗口"); //窗口
JPanel panel=new JPanel(); //面板
private JLabel label=new JLabel("没有操作"); //标签
private JButton button=new JButton("确定"); //按钮
public Test(){
panel.add(label); //将标签加到面板中
panel.add(button); //将按钮加到面板中
frame.add(panel); //将面板加到窗口中
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
label.setText("点击了按钮");
}
});
}
}
泛型是JDK5.0后出现的新概念,泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数。当这种参数类型用在类中时,该类就被称为泛型类。
泛型类定义的格式:
[类修饰符] class类名<类型参数列表> [extends 父类名] [implement 接口名列表]{
类体
}
类型参数的定义方式有以下三种*:
类型变量标识符
类型变量标识符 extends 父类名
类型变量标识符 extends 父类名1 & 父类名2 ...
规则和限制:
泛型的类型参数可以有多个。
泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
在static方法中不可以使用泛型,泛型变量也不可以用static关键字来修饰。
不可以定义泛型数组。
代码:
class MyClass {
private T a;
private U b;
public MyClass(T a, U b) {
this.a = a;
this.b = b;
}
public T getA() {
return a;
}
public U getB() {
return b;
}
}
public class Test {
public static void main(String[] args) {
MyClass m = new MyClass("abc", 123);
String s = m.getA();
System.out.println(s);
Integer i = m.getB();
System.out.println(i);
}
}
而不用泛型的代码:
class MyClass {
private Object a;
private Object b;
public MyClass(Object a, Object b) {
this.a = a;
this.b = b;
}
public Object getA() {
return a;
}
public Object getB() {
return b;
}
}
public class Test {
public static void main(String[] args) {
MyClass m = new MyClass("abc", 123);
String s = (String) m.getA();
System.out.println(s);
Integer i = (Integer) m.getB();
System.out.println(i);
}
}