一、说明
二、解释
- 1.封装
1.1. private、protected、public访问权限说明
1.2 成员变量、局部变量、静态变量
1.3 Java变量、常量及数据类型
1.4 基本数据类型与引用数据类型的区别 - 2.继承
2.1 为什么Java的类只能单一继承?以及Java类可以集成多个接口?
2.2 重载、重写
2.3 接口
2.4 抽象类
2.5 抽象类与接口的区别
2.6 构造方法与实例方法的区别
2.7 Java显示调用与隐式调用
2.8 Java super与this关键字的用法 - 3.多态
3.1 向上转型与向下转型
3.2 多态实例
3.3 泛型
一、说明
Java作为面向对象的语言自然支持封装、继承及多态这三个特性,下面就对这三种特性在Java中的表现,以及相关联的一些概念进行解释说明。
二、解释
1、封装
(1)利用抽象数据类型将数据和基于数据的操作封装在一起,数据被保护在抽象数据类型的内部,系统的其他部分只有通过分装在数据外面的被授权的操作,才能够与这个抽象类型交互。
(重点:抽象数据类型、数据、基于数据的操作)
(2)封装是一种信息隐藏技术,在java中通过关键字private,protected和public实现封装。
(3)封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过严格的接口控制。封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。
封装的优点:
(1)良好的封装可以减少耦合。
(2)类内部的结构可以自由修改。
(3)可以对成员变量进行更加精准的控制。
(4)隐藏信息,实现细节。
封装的步骤:
(1) 修改属性的可见性来限制对属性的访问(一般限制为private)1
2
3
4public class Person {
private String name;
private int age;
}
(2)对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class Person{
private String name;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public void setName(String name){
this.name = name;
}
}
(3)在外部类中调用1
2
3
4
5
6
7
8
9public class Run{
public static void main(String args[]){
Person p=new Person();
p.setAge(8);
p.setName("zx");
System.out.println("Name:"+p,getName()+"Age:"+p.getAge());
}
}
1.1 private、protected、public访问权限说明
- private:只可以被本类访问
- protected:只可以被本类及被继承它的类访问,同包中的类也可以访问。
- public:可以被所有类访问
1.2 成员变量、局部变量、静态变量
Java中通过类来描述事物(对象),而类中会描述事物(对象)属性和行为。类是一个抽象的概念,而其具体表现为对象,对象则包含属性和行为。
(1)成员变量:事物的属性,而成员方法就是事物的行为。
(2)局部变量:在方法中的定义的变量。
成员变量与局部变量的区别:
成员变量:- 在类中定义,在整个类中都可以被访问。
- 随着对象的建立而建立,随着对象的消失而消失,存在于对象所在的堆内存中。
- 有默认的初始化值。
局部变量:- 只定义在局部范围内,如:方法内、语句内等,只在所属区域内有效。
- 存在于栈内存中,作用的范围结束,变量会自动释放空间。
- 没有默认初始化值
(3)静态变量:由static修饰的变量称为静态变量,其实质上就是一个全局变量。如果某个内容是被所有对象所共享,那么该内容就应该用静态修饰;没有被静态修饰的内容,其实是属于对象的特殊描述。
成员变量与静态变量的区别:
- 生命周期不同:
成员变量随着对象的创建而存在,随着对象被回收而释放。
静态变量随着类的加载而存在,随着类的消失而消失。 - 调用方式不同:
成员变量只能被对象调用。
静态变量可以被对象调用,还可以被类名调用。 - 别名不同:
成员变量也称为实例变量。
静态变量也称为类变量。 - 数据存储位置不同:
成员变量存储在堆内存的对象中,所以也叫对象的特有数据。
静态变量数据存储在方法区(共享数据区)的静态区,所以也叫对象的共享数据。1.3 Java变量、常量及数据类型
(1)变量:在程序设计中,变量是一种存储数据的载体。计算机中的变量是实际存在的数据,与数学方程中抽象的“变量”存在本质区别。变量的数值可以被读取和修改,是所有计算的基础。 - 变量的声明:数据类型 + 变量名 = 数值或表达式
变量的命名规范:- 必须以字母、下划线或美元符号开头。
- 只能出现字母、下划线、数字和美元符号,不得出现任何其他符号或空格。
- 不能使用保留字作为名字。
- 在同一使用范围内,所有变量、函数、类、对象等的名称不得重复。变量的类型转换:有精度低到精度高转换。
(2)常量:指不能改变的量, 在Java中用final标志,声明方式和变量类似。通常以大写的方式声明。
(3)数据类型:Java一共有八大基本数据类型以及引用数据类型。
八大基本数据类型:
数值型 所占字节数 字符型 所占字节数 布尔型 所占字节数 浮点型 所占字节数
byte 1 char 2 boolean 1 float 4
short 2 double 8
int 4
long 8引用类型:类、接口类型、数组类型、枚举类型、注解类型。
1.4 基本数据类型与引用数据类型的区别
(1)基本数据类型在被创建时,在栈上给其划分一块内存,将数值直接存储在栈上。
(2)引用数据类型在被创建时,首先要在栈上给其引用(句柄)分配一块内存,而对象的具体信息都存储在堆内存上,然后由栈上面的引用指向堆中对象的地址。
2.继承
继承是指一个对象直接使用另一个对象的属性和方法。Java语言给用户提供了一系列的类,并且Java语言的类很有层次结构,子类可以继承父类的属性和方法。Java语言只支持单一继承,这样就大大降低了复杂度,但在Java语言中,可以用接口来实现多重继承。
(1)继承的特点:
- 子类拥有父类非 private 的属性、方法。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。(重写)
- Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 A 类继承 B 类,B 类继承 C 类,所以按照关系就是 C 类是 B 类的父类,B 类是 A 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
- 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
(2)继承所使用的关键字
extends:在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
1
2
3
4public class father{
}
public class son extends father{
}implements:使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
1
2
3
4
5
6
7
8
9
10
11public interface A {
public void eat();
public void sleep();
}
public interface B {
public void show();
}
public class C implements A,B {
}
2.1 为什么Java的类只能单一继承?以及Java类可以集成多个接口?
(1)假设son1和son2继承father类并重写父类的同一个方法(重写的行为不一样),此时有个grandson同时继承son1和son2,当在grandson中调用继承的方法时,他会不知道该调用son1,还是son2中的重写的方法。为了避免这个情况,所以使用单一继承。同时,这样也可以增加程序的安全性。
(2)Java接口是行为性的,也就是说它只是定义某个行为的名称,而具体的行为的实现是集成接口的类实现的,因此就算两个接口中定义了两个名称完全相同的方法,当某个类去集成这两个接口时,类中也只会有一个相应的方法,这个方法的具体实现是这个类来进行编写的,所以并不会出现结构混乱的情况。
2.2 重载、重写
(1)重载:重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
- 重载的规则:
-被重载的方法必须改变参数列表(参数个数或类型不一样);
-被重载的方法可以改变返回类型;
-被重载的方法可以改变访问修饰符;
-被重载的方法可以声明新的或更广的检查异常;
-方法能够在同一个类中或者在一个子类中被重载。
-无法以返回值类型作为重载函数的区分标准。
(2)重写:重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。
- 重写的规则:
-参数列表必须完全与被重写方法的相同;
-返回类型必须完全与被重写方法的返回类型相同;
-访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该
方法就不能声明为protected。
-父类的成员方法只能被它的子类重写。
-声明为final的方法不能被重写。
-声明为static的方法不能被重写,但是能够被再次声明。
-子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
-子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
-重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,
或者比被重写方法声明的更广泛的强制性异常,反之则可以。
-构造方法不能被重写。
-如果不能继承一个方法,则不能重写这个方法。
(3)重载与重写区别
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
- 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
- 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
- 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
2.3 接口
- 接口,在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。
- 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
- 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
(1)接口与类的相似处
- 一个接口可以有多个方法。
- 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
- 接口的字节码文件保存在 .class 结尾的文件中。
- 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
(2)接口与类的不同之处
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
(3)接口特性
- 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
- 接口中的方法都是公有的。
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
(4)接口与抽象类的区别
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块 和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
(5)接口的声明
- 具有 public 访问控制符的接口,允许任何类使用;没有指定 public 的接口,其访问将局限于所属的包。
- 方法的声明不需要其他修饰符,在接口中声明的方法,将隐式地声明为公有的(public)和抽象的(abstract)。
- 在 Java 接口中声明的变量其实都是常量,接口中的变量声明,将隐式地声明为 public、static 和 final,即常量,所以接口中定义的变量必须初始化。
- 接口没有构造方法,不能被实例化。
1
2
3
4
5public interface NameOfInterface
{
//任何类型 final, static 字段
//抽象方法
}
(6)接口的实现
当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。1
2
3public class A implements Interface{
//实现相应接口的方法
}
- 重写接口中的方法所需要注意的规则
类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
如果实现接口的类是抽象类,那么就没必要实现该接口的方法。 - 实现接口时所要注意的规则
一个类可以同时实现多个接口。
一个类只能继承一个类,但是能实现多个接口。
一个接口能继承另一个接口,这和类之间的继承比较相似。但接口可以多继承。
(7)接口的继承
- 单一继承
1
2
3
4
5
6
7
8
9
10
11//一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,
//子接口继承父接口的方法。
public interface father{
public void run();
public void read();
}
public interface son extends father{
public void simle();
public void say();
}
在实现son接口时,要实现四个方法,算上继承的父类的。
(8)标记接口
最常用的继承接口是没有包含任何方法的接口。
标记接口是没有任何方法和属性的接口。它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。1
2
3package java.util;
public interface EventListener
{}
作用:
- 建立一个公共的父接口:
正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。 - 向一个类添加数据类型:
这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。
使用标记接口的唯一目的是使得可以用 instanceof 进行类型查询,例如;1
if(obj instanceof Cloneable) {}
2.4 抽象类
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//抽象类一般用于不能明确声明对象,必须被继承,不能实例化对象。
public abstract class father{
}
public class son extends father{
}
//正确的调用方法
public class Demo{
son s=new son();
father f=new son();
father f1=new father();//错误示范
}
(1)抽象方法
如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。Abstract关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。且包含抽象方法的类也必须声明为抽象类。
- 声明抽象方法所要的条件
如果一个类包含抽象方法,那么该类必须是抽象类。
任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。1
2
3
4public abstract class father{
public abstract void son();
}
注意:
- 抽象类不能被实例化,如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
- 构造方法,类方法(用static修饰的方法)不能声明为抽象方法。
- 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。
2.5 抽象类与接口的区别
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块 和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
2.6 构造方法与实例方法的区别
构造方法:类里的一个特殊的方法,不能有返回值。是在类实例化时所要执行的方法,目的是对该类进行一些参数的初始化。且构造方法不可以被子类继承。
实例方法:就是指类中的除去构造方法的那些方法。
- 构造方法的主要作用:
1.创建对象。任何一个对象创建时,都需要初始化才能使用,所以任何类想要创建实例对象就必须具有构造函数。(即下面所提到的Example e=new Example())
2.对象初始化。构造函数可以对对象进行初始化,并且是给与之格式(参数列表)相符合的对象初始化,是具有一定针对性的初始化函数。(即下面的Example e=new Example(1,2,3)) - 区别:主要在三个方面,修饰符、返回值、命名
1.构造方法可以有访问修饰符,private、protected、public,但不可以有任何非访问性质的修饰符,如:static、final、synchronized、abstract等。
2.构造方法无任何返回值类型,连void也不可以。实例方法可以有任何返回值类型。
3.命名方面,构造方法与类名相同,实例方法可以与类名相同,但一般会有所区别。
注:如果一个类没有声明构造方法,java会自动声明一个隐式的构造方法。 - 构造方法的声明与调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42//1.隐式默认构造方法
public class Example{}
//2.显示默认构造方法
public class Example{
public Example(){}
}
//3.自定义构造方法,无默认构造方法
public class Example{
private int a;
private int b;
private int c;
public Example(int a,int b,int c){
this.a=a;
this.b=b;
this.c=c;
}
........//get和set方法
}
//关于实例化以及有参与无参的区别
1.Example e1=new Example();//正确
2.Example e2=new Example();//正确
3.Example e3=new Example();//错误,由于没有默认的构造方法。
正确的做法:Example e3=new Example(1,2,3);
或者同时编写e3和e2,此时若是想用无参构造函数实例化对象,则需要在类中声明get和set方法。
例如:
Example e=new Example();
e.setA(1);
e.setB(2);
e.setC(3);
之后可以用get获得在具体的值
```
<Br>
注意:如果使用了有参构造方法,JVM就不会生成默认的构造方法,所以为了使用无参数的构造函数,就要显示的声明一个无参的构造函数。
##### 2.7 10.Java显示调用与隐式调用
关于继承的调用顺序:静态初始化块(先父后子)-->父类非静态初始化块-->父类构造方法-->子类的非静态初始化块-->子类构造器
<Br>
//父类
public class Father{
static{
System.out.println(“父类静态初始化块”);
}
{
System.out.println(“父类非静态初始化块”);
}
public Father(){
System.out.println("父类无参构造方法");
}
public Father(int a){
System.out.println(“父类有参构造方法: a=”+a);
}
}
//子类
public class Son extends Father{
static{
System.out.println(“子类静态初始化块”);
}
{
System.out.println(“子类非静态初始化块”);
}
public Son(){
this(2);//调用本类的有参构造方法
System.out.println(“子类无参构造方法”);
}
public Son(int a){
super(a);//显示调用父类有参构造方法
System.out.println(“子类有参构造方法: a=”+a);
}
}
public class Example{
public staic void main(String[] args){
Son s=new Son(1)//实例化有参的对象
Son s1=new Son();//实例化无参的对象
}
}
/////////////
输出结果:
父类静态初始化块
子类静态初始化块
父类非静态初始化块
父类含参构造方法:a=1
子类非静态初始化块
子类含参构造方法: a=1
父类非静态初始化块
父类含参构造方法:a=2
子类非静态初始化块
子类含参构造方法: a=2
子类无参构造方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42<Br>
注:静态初始化块只调用一次。在子类的构造器中可以通过super来显式调用父类的构造器(参数形式调用不同的构造器),可以通过this来调用该类重载的其他构造器,而具体调用哪个构造器决定于调用时的参数类型。如果一个构造器中没有super调用,则会隐式调用父类的无参构造器。super和this的调用只能在构造器中,而且都必须作为构造器中的第一行,因此super和this不会同时出现在同一个构造器中。
##### 2.8 Java super与this关键字的用法
* this:
this只能用于方法方法体内。当一个对象创建后,Java虚拟机(JVM)就会给这个对象分配一个引用自身的指针,这个指针的名字就是this。因此,this只能在类中的非静态方法中使用,静态方法和静态的代码块中绝对不能出现this。并且this只和特定的对象关联,而不和类关联,同一个类的不同对象有不同的this。
1.通过this调用另一个构造方法,用发是this(参数列表),这个仅仅在类的构造方法中,别的地方不能这么用。且用在方法的第一行。
2.函数参数或者函数中的局部变量和成员变量同名的情况下,成员变量被屏蔽,此时要访问成员变量则需要用“this.成员变量名”的方式来引用成员变量。当然,在没有同名的情况下,可以直接用成员变量的名字,而不用this。
3.在方法中,需要引用该方法所属类的当前对象时候,直接用this。
* super:
1.在子类构造方法中要调用父类的构造方法,用“super(参数列表)”的方式调用,参数不是必须的。同时还要注意的一点是:“super(参数列表)”这条语句只能用在子类构造方法体中的第一行。
2.当子类方法中的局部变量或者子类的成员变量与父类成员变量同名时,也就是子类局部变量覆盖父类成员变量时,用“super.成员变量名”来引用父类成员变量。当然,如果父类的成员变量没有被覆盖,也可以用“super.成员变量名”来引用父类成员变量,不过这是不必要的。
3.当子类的成员方法覆盖了父类的成员方法时,也就是子类和父类有完全相同的方法定义(但方法体可以不同),此时,用“super.方法名(参数列表)”的方式访问父类的方法。
#### 3.多态
多态是指一个程序中同名的多个不同方法共存的情况,即一个对外接口,多个内在实现方法,面向对象的程序中多态的情况有多种,可以通过子类对父类方法的覆盖实现多态,也可以利用重载在 同一个类中定义多个同名的不同方法来实现多态。多态的特点使得他们不需要了解对方的具体细节,就可以很好的共同工作,这个优点,对程序的设计、开发和维护都有很大的好处。
* 多态的优点
1. 消除类型之间的耦合关系
2. 可替换性
3. 可扩充性
4. 接口性
5. 灵活性
6. 简化性
* 多态存在的三个必要条件
1. 继承
2. 重写
3. 父类引用指向子类的对象( Father f=new Son(); )
##### 3.1 向上转型与向下转型
* 向上转型:父类引用指向子类对象。父类引用指向子类对象,将会丢失子类和父类中不一致的方法,并且父类引用调用的变量只能是父类中的。
注:此时调用次序是调用子类和父类相同的(同名同参)的方法,不可以调用子类中父类没有的方法,如果子类没有所调用的方法,就会去父类中找(这是由于子类继承了父类的方法)。
<Br>
class A {
public void show(B b){
System.out.println(“a and b”);
}
public void show(){
System.out.println("only a");
}
}
class B extends A {
public void show(B b){
System.out.println(“b and b”);
}
public void show(int a){
}
}
class Test{
public static void main(String[] args) {
A a = new B();
a.show(new B());
a.show();
}
}
//输出
b and b
only a
//////////////////////////////////
public class Father{
public void eat()
{
System.out.println(“父类的eat”);
}
}
class Son extends Father{
@Override
public void eat()
{
System.out.println(“子类的eat”);
}
public void run()
{
System.out.println(“子类的run”);
}
}
////////////
class People{
public void eat(){
System.out.println(“父类的吃”);
}
}
class Man extends People{
@Override
public void eat()
{
System.out.println(“Man的eat”);
}
}
class Woman extends People{
@Override
public void eat()
{
System.out.println(“Woman的eat”);
}
}
public class Test{
public static void main(String[] args){
Father f=new Son();//向上转型,f指向子类的对象
f.eat();//调用子类的eat
f.run();//错误,由于向上转型而失去了子类的run方法
Eat(new Man());//传入的参数子类
Eat(new Woman());
}
public static void Eat(People p){//方法的参数是父类
p.eat();
}
}1
2
3
4
5
6
7
8
9
10
11<Br>
向上转型的好处:
1.提高代码的可扩展性:即指调用父类和子类都有的子类的方法,要是想调用子类特有的方法就直接 Son s=new Son(); s.run();
2.使代码更加简洁:以上面的Eat为例,方法里不用重复写很多调用代码。
* 向下转型:子类引用指向父类对象
<Br>
public class Father{
public void eat()
{
System.out.println(“父类的eat”);
}
}
public Son extends Father{
@Override
public void eat()
{
System.out.println(“子类的eat”);
}
public void run()
{
System.out.println(“子类的run”);
}
}
public class Test{
public static void main(String[] args){
Father f=new Son();//向上转型
f.eat();//调用子类的eat
Son ff=(Son)f;//向下转型,还是指向子类的对象
ff.eat();//调用的子类的方法
ff.run();
//不安全的向下转型
Father f1=new Father();
Son f2=(Son)f1;//编译没有错,但运行会出错,f1是父类对象,子类f2对象肯定不可以指向父类对象
f2.eat();
f2.run();
}
}1
2
3
4
5
6<Br>
注:为了安全的类型转换,最好先用 if(A instanceof B) 判断一下。
向下转型的好处:以父类为基础,使得其他子类的方法更加清晰的实现。或者模糊的实现。
<Br>
public void Eat(People p){
if(p instanceof Man){ //如果p是Man的实例
Man m = (Man)p;
m.eat();
m.run();//Man有一个跑的方法
}
if(p instanceof Woman){
Woman w = (Woman)p;
w.eat();
System.out.println(“购物”); //女人都喜欢购物
}
p.eat();//老人,小孩什么的就只有吃这个动作
}
Eat(new Man());//根据具体的子类参数来实现不同的动作1
2
3
4
5
6
7
<Br>
补充:如果不用转型则会使代码更加复杂
<Br>
public void Eat(People p){
if(p instanceof Man){ //如果p是Man的实例
Man m = (Man)p;
m.eat();
m.run();//Man有一个跑的方法
}
if(p instanceof Woman){
Woman w = (Woman)p;
w.eat();
System.out.println(“购物”); //女人都喜欢购物
}
p.eat();//老人,小孩什么的就只有吃这个动作
}
Eat(new Man());//根据具体的子类参数来实现不同的动作1
2
3
4
5
6
7
8
<Br>
##### 3.2 多态实例
当父类对象引用变量引用子类对象时,被引用对象的类型决定了调用谁的成员方法,引用变量类型决定可调用的方法。如果子类中没有覆盖该方法,那么会去父类中寻找(这是由于子类继承了父类的方法)。结合上面的向上转型的注意来理解。
<Br>
package pers.zx.test;
public class Test{
public static void main(String[] args) {
A a1=new A();//实例化一个父类对象
A a2=new B();//向下转型,指向子类引用
B b=new B();//实例化子类对象
C c=new C();
D d=new D();
//a1去找的是A类,然后在具体判断参数是否符合
System.out.println(""+a1.show(b));//A obj=new B();a1=new A()
System.out.println(""+a1.show(c));//A obj=new C();向上到B的父类找
System.out.println(""+a1.show(d));//D obj=new D();
//向上转型
/*
* 当父类对象引用变量引用子类对象时,被引用对象的类型决定了调用谁的成员方法,引用变量类型决定可调用的方法。
* 如果子类中没有覆盖该方法,那么会去父类中寻找(这是由于子类继承了父类的方法)。
* */
System.out.println(""+a2.show(b));//由于向上转型,所以调用与父类相同的在子类中的方法
System.out.println(""+a2.show(c));//由于向上转型,所以调用与父类相同的在子类中的方法
System.out.println(""+b.show(b));//去B类找与参数b对应的,找到调用,没找到就去父类中找(继承了父类的方法)
System.out.println(""+b.show(c));
System.out.println(""+b.show(d));//调用继承父类中的方法
}
}
class A {
public String show(D obj) {
return "A and D";
}
//重载
public String show(A obj) {
return "A and A";
}
public void e() {
System.out.println("A类e");
}
}
class B extends A{
public String show(B obj) {
return “B and B”;
}
public String show(A obj) {
return "B and A";
}
public void e() {
System.out.println("B类e");
}
public void d() {
System.out.println("B类d");
}
}
class C extends B{}
class D extends B{}
1 | <Br> |
1.泛型类的定义
[修饰符] class 类名
2.泛型接口的定义
[public] interface 接口名
3.泛型方法的定义
[public] [static]1
2
3<Br>
定义泛型之后,就可以在代码中使用类型参数T来表示一种数据的类型而非数据的值,即T可以看作泛型类的一种“类型形式参数”。在定义类型参数后,就可以在类体或者接口体中定义的各个部分直接使用这些类型参数。而在应用这些具有泛型特性的类或接口时,需要指明实际的具体类型,即用“类型实际参数”来替换“类型形式参数”,也就是说,用泛型类创建的对象就是在类体内的每个类型参数T处分别用这个具体的实际类型替代。泛型的实际参数必须是类类型,利用泛型类创建的对象称之为泛<Br>
/*
* 泛型实在编译时检查和处理类型转换的,不是在运行时,
* 由于下面ArrayList在不定义泛型时可以添加任何基本类型,在定义泛型后,就会
* 限制相应的类型,防止添加不符的类型。提高程序的安全性。
*
* 同样体现了泛型的实质就是将数据的类型参数化,根据想要的数据类型来添加参数。
* 但我们通常再用泛型时会将它定义为任意的参数类型,然后具体地方具体调用。即具体传参
* */
List<Integer> IntegerData=new ArrayList<Integer>();
List IntegerData1=new ArrayList();
IntegerData1.add(1);
IntegerData1.add("1");
IntegerData.add(1);//成功
IntegerData.add("1");//编译时错误,与泛型类型不符,添加不成功
1 | <Br> |
//在实例化类型时必须指定T的具体类型
public class Generic
private T key;//key这个成员变量的类型为T,T的类型由外部指定
public Generic(T key) {//泛型构造方法形参key的类型也是T,T的类型由外部指定
this.key=key;
}
public T getKey() {//泛型方法的返回类型为T,T由外部指定
return key;
}
public static void main(String[] args) {
/*
* 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
* 传入的实参类型需与泛型的类型参数类型相同
* */
Generic<Integer> a=new Generic<Integer>(1);//以Integer类型为实际参数,实例化泛型类
Generic<String> s=new Generic<String>("zx");//以String类型为实际参数,实例化泛型类
//由于构造函数在实例化时会初始化参数,所以key已经有值
System.out.println(a.getKey());
System.out.println(s.getKey());
//在调用泛型类实例化对象时不一定要一定传入具体的类型实参,可以直接实例化
//此时,该泛型类属于任何类型,比如
Generic g1=new Generic(123);
Generic g2=new Generic("123");
Generic g3=new Generic(1.23);
//但这样就失去了泛型本来应有的作用
}
}1
2
3
4
5
6
7
8
<Br>
注意:1.泛型类的类型参数只能是类类型,不能是简单类型。
2.不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。
<Br>
if(ex_num instanceof Generic
} //Generic1
2
3
4<Br>
2.泛型接口:与泛型类的声明方法类似,常被用于各种类的生产器中。
<Br>
import java.util.Random;
//泛型接口
public interface Generator
public T next();
}
//泛型接口实现,未传入实参
//实现泛型接口时,相应的类也要加类型参数
//具体实现类的时候,只要加上相应的实际参数就好了
//AnythingGenerator
//此时里面的next方法的返回值类型就是Integer
class AnythingGenerator
@Override
public T next() {
// TODO Auto-generated method stub
return null;
}
}
//泛型接口实现,传入实参
class AnythingGenerator1 implements Generator
private String[] something=new String[] {"1","2","3"};
@Override
public String next() {
// TODO Auto-generated method stub
Random rand=new Random();
return something[rand.nextInt(2)];
}
}1
2
3
4
5
6
7<Br>
3.泛型方法:是在调用方法的时候指明泛型的具体类型 。
<Br>
public class GenericMethod {
////////////////////////////////////////////////////////////////////////////////
// 1.public 与返回值中间
// 2.只有声明了
// 3.
// 4.T可以替换为E、K、V
public
return null;
}
////////////////////////////////////////////////////////////////////////////////
public class Generic<T>{
private T key;
//构造函数
public Generic(T key) {
this.key=key;
}
//这不是泛型方法,是泛型类中的一个成员方法,只不过
//返回的类型与泛型类声明的相同
public T getKey() {
return key;
}
/**
* 错误示范
* 由于在泛型类中并未声明泛型E,所以在使用E做类型参数和返回值时编译器无法识别
* public E getKey(E key){
* this.key=key
* }
*/
///////////////////////////////////////////////////////////////////////////////
//泛型方法
/*在public与返回值类型之间有<T>,这表明这是一个泛型方法
* ,并声明了一个泛型类型参数T,这个T可以出现在这个泛型方法的任何位置
* 泛型的数量也可以为任意多个
*
* 如: public <T,K...> K showKeyName(Generic<T> container){
* ......
* }
* */
public <T> T showKeyName(Generic<T> container) {
System.out.println("container key:"+container.getKey());
//说明泛型方法的特性
T test =container.getKey();
return test;
}
//普通方法,只是用了Number做泛型参数
public void showKeyValue1(Generic<Number> obj) {
System.out.println("key="+obj.getKey());
}
//这也不是一个泛型方法,只是用了泛型通配符?
//?是一种类型实参,可以看作Number等所有类的父类
public void showKeyValue2(Generic<?> obj) {
System.out.println("key="+obj.getKey());
}
}
/**
* 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "
* 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。
* 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。
public <T> T showKeyName(Generic<E> container){
...
}
*/
/**
* 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "
* 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。
* 所以这也不是一个正确的泛型方法声明。
public void showkey(T genericObj){
}
*/
}
1 |
|
/**
- @author zx
泛型类中的泛型方法
*/
public class GenericMethodInClass {public static void main(String[] args) {
Son s=new Son(); Person p=new Person(); GenerateTest<Father> generateTest=new GenerateTest<Father>(); //Son 是Father的子类,所以这里可以 generateTest.show1(s); //generateTest.show1(p);//会报错,由于泛型类实参指定的是Father,而传入的实参是Person //泛型方法,使用这两个方法都可以成功 generateTest.show2(s); generateTest.show2(p); //同理也成功 generateTest.show3(s); generateTest.show3(p);
}
}
class Father{
@Override
public String toString() {
// TODO Auto-generated method stub
return "father";
}
}
class Son extends Father{
@Override
public String toString() {
// TODO Auto-generated method stub
return "son";
}
}
class Person{
@Override
public String toString() {
// TODO Auto-generated method stub
return "Person";
}
}
//泛型类
class GenerateTest
public void show1(T t) {
System.out.println(t.toString());
}
//声明泛型方法,使用泛型E,这种泛型E可以是任意类型。可以与T类型相同,也可以不同
//由于泛型方法在声明时会声明<E>,因此即使在泛型类中未声明泛型,编译器也能够识别泛型方法中识别的泛型
public <E> void show2(E t) {
System.out.println(t.toString());
}
//在泛型类中声明一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public <T> void show3(T t) {
System.out.println(t.toString());
}
}1
2
3
4
5<Br>
5.泛型方法与可变参数
<Br>
public class Test {
public <T> void display(T... args) {
for(T t:args) {
System.out.println("t="+t);
}
}
public static void main(String[] args) {
Test t=new Test();
t.display("1",2,3,"dajfjl");
}
}1
2
3
4
5
6
7
8
6.静态方法与泛型
静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
<Br>
public class StaticGenerator
….
….
/**
* 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
"StaticGenerator cannot be refrenced from static context"
*/
public static <T> void show(T t){
}
}1
2
3
4
5
6
7
8
9
10
7.泛型的上下边界
在使用泛型的时候,我们还可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只传入某种类型的父类或某种类型的子类。
注意:泛型的上下边界添加,必须与泛型的声明在一起 。
<Br>
//1.方法参数限制
//为泛型添加上边界,即传入的类型实参必须是指定类型的子类型
//泛型边界测试
public void display(Generic<? extends Number> obj) {
System.out.println(“key=”+obj.getKey());
}
public static void main(String[] args){
//边界测试
Generic
Generic
//e1.display(e1);//错误,由于String不属于Number
e2.display(e2);
}
//2.泛型方法
//泛型方法中添加上下边界时,必须在权限声明与返回值之间的
//声明的时候添加
// public
public
T test=container.getKey();
return null;
}
//3.泛型类参数限制
public class Generic1
private T key;//key这个成员变量的类型为T,T的类型由外部指定
public Generic1(T key) {//泛型构造方法形参key的类型也是T,T的类型由外部指定
this.key=key;
}
public T getKey() {//泛型方法的返回类型为T,T由外部指定
return key;
}
public static void main(String[] args) {
Generic1<Integer> a=new Generic1<Integer>(1);
//Generic1<String> a1=new Generic1<String>(1);//错误,原因和上面相同
}
}1
2
3
4
5
6
7
8
9
10///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
8.泛型数组
在Java中不能创建一个确切的泛型类数组
1.List
2.使用通配符创建泛型数组是可以的
List<?>[] l=new ArrayList<?>[10];
或者
List`
9.泛型通配符
(1)Generic
(2)类型通配符一般是使用?代替具体的类型实参,此处’?’是类型实参,而不是类型形参 。再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。
类型通配符可以解决当具体类型不确定的时候,这个通配符就是 ? ;当操作类型时,不需要使用类型的具体功能时,只使用Object类中的功能。那么可以用 ? 通配符来表未知类型。
Generic<?> obj
10.泛型小结
泛型方法能使方法独立于类而产生变化,在编程时能使用泛型方法的地方要尽量使用泛型方法。以简化代码的开发,保证代码的质量。