Java内部类、泛型类和接口
本讲介绍Java面向对象的高级部分。对这一部分内容仅作必要而非深入的介绍。
一、Java内部类
在Java 语言中的类可以嵌套定义,允许在另外一个类中定义一个类,即在一个类的类体中可以嵌套(nested)定义另外一个类。外层的称为外部类(outer class),也可以叫做封闭类;内部的称为内部类(Inner Classes),有时也称为嵌套类(Nested Class)。内部类可以是静态(static)的,可以使用 public、protected 和 private 访问控制符,而外部类只能使用 public,或者默认。
例如:
class OuterClass {
// code
class InnerClass {
// code
}
}
Inner Classes 不能定义为static,不能有static方法和static初始化语句块。
内部类使用场景:当一个类只在某个类中使用,并且不允许除外部类外的其他类访问时,常用于GUI事件监听。
内部类的分类
☆成员式内部类
☆局部内部类
☆匿名内部类
有些人认为内部类的语法很复杂,实际开发中也较少用到,在此仅简要介绍。
成员式内部类
在外部类内部直接定义(不在方法内部或代码块内部)的类就是成员式内部类,它可以直接使用外部类的所有变量和方法,即使是 private 的。外部类要想访问内部类的成员变量和方法,则需要通过内部类的对象来获取。如:
public class Outer {
private int size;
public class Inner {
private int counter = 10;
public void doStuff() {
size++;
}
}
public static void main(String args[]) {
Outer outer = new Outer();
Inner inner = outer.new Inner();
inner.doStuff();
System.out.println(outer.size);
System.out.println(inner.counter);
// 下句编译错误,故注释掉,因为外部类不能访问内部类的变量
//System.out.println(counter);
}
}
输出:
1
10
注意:必须先有外部类的对象才能生成内部类的对象,因为内部类需要访问外部类中的成员变量,成员变量必须实例化才有意义。
成员式内部类如同外部类的一个普通成员。
成员式内部类可以使用各种修饰符,包括 public、protected、private、static、final 和 abstract,也可以不写。
若有 static 修饰符,就为类级,否则为对象级。类级可以通过外部类直接访问,对象级需要先生成外部的对象后才能访问。
非静态内部类中不能声明任何 static 成员。
内部类可以相互调用,例如:
class A {
// B、C 间可以互相调用
class B {}
class C {}
}
局部内部类
局部内部类(Local class)是定义在代码块中的类。它们只在定义它们的代码块中是可见的。
局部类有几个重要特性:
仅在定义了它们的代码块中是可见的;
可以使用定义它们的代码块中的任何局部 final 变量;
局部类不可以是 static 的,里边也不能定义 static 成员;
局部类不可以用 public、private、protected 修饰,只能使用缺省的;
局部类可以是 abstract 的。
例子
public class OuterA {
public static final int TOTAL_NUMBER = 5;
public int id = 123;
public void func() {
final int age = 15;
String str = "测试";
class Inner {
public void innerTest() {
System.out.println(TOTAL_NUMBER);
System.out.println(id);
// System.out.println(str);不合法,只能访问本地方法的final变量
System.out.println(age);
}
}
new Inner().innerTest();
}
public static void main(String[] args) {
OuterA OuterA = new OuterA();
OuterA.func();
}
}
运行结果:
5
123
15
匿名内部类
匿名内部类是局部内部类的一种特殊形式,也就是没有变量名指向这个类的实例,而且具体的类实现会写在这个内部类里面。
注意:匿名类必须继承一个父类或实现一个接口。
例子
abstract class PersonOne {
public abstract void eat();
}
class Child extends PersonOne {
public void eat() {
System.out.println("eat something");
}
}
public class DemoOne {
public static void main(String[] args) {
PersonOne p = new Child();
p.eat();
}
}
运行结果:
eat something
二、泛型类
所谓“泛型”,就是“宽泛的数据类型”,任意的数据类型。
假如我们现在要定义一个类来表示坐标,要求坐标的数据类型可以是整数、小数和字符串,例如:
x = 10、y = 10
x = 12.88、y = 129.65
x = "东京180度"、y = "北纬210度"
针对不同的数据类型,除了借助方法重载,还可以借助自动装箱和向上转型。我们知道,基本数据类型可以自动装箱,被转换成对应的包装类;Object 是所有类的祖先类,任何一个类的实例都可以向上转型为 Object 类型,例如: int --> Integer --> Object
还可以使用泛型类。
泛型类声明格式为:
class 类名<泛型列表>
如:class A<E,F>
其中,A是泛型类的名称,E、F是泛型类的参数,即泛型类的参数类型没有指定。它可以是任何引用类型,但不能是基本数据类型。
例子
public class DemoGeneric {
public static void main(String[] args){
// 实例化泛型类
PointOne<Integer, Integer> p1 = new PointOne<Integer, Integer>();
p1.setX(10);
p1.setY(20);
int x = p1.getX();
int y = p1.getY();
System.out.println("This Point is:" + x + ", " + y);
PointOne<Double, String> p2 = new PointOne<Double, String>();
p2.setX(25.4);
p2.setY("东京180度");
double m = p2.getX();
String n = p2.getY();
System.out.println("This Point is:" + m + ", " + n);
}
}
// 定义泛型类
class PointOne<T1, T2>{
T1 x;
T2 y;
public T1 getX() {
return x;
}
public void setX(T1 x) {
this.x = x;
}
public T2 getY() {
return y;
}
public void setY(T2 y) {
this.y = y;
}
}
运行结果:
This point is:10, 20
This point is:25.4, 东京180度
上面的代码在类名后面多出了 <T1, T2>,T1, T2 是自定义的标识符,也是参数,用来传递数据的类型,而不是数据的值,我们称之为类型参数。在泛型中,不但数据的值可以通过参数传递,数据的类型也可以通过参数传递。T1, T2 只是数据类型的占位符,运行时会被替换为真正的数据类型。
传值参数(我们通常所说的参数)由小括号包围,如 (int x, double y),类型参数(泛型参数)由尖括号包围,多个参数由逗号分隔,如 <T> 或 <T, E>。
类型参数需要在类名后面给出。一旦给出了类型参数,就可以在类中使用了。类型参数必须是一个合法的标识符,习惯上使用单个大写字母,通常情况下,K 表示键,V 表示值,E 表示异常或错误,T 表示一般意义上的数据类型。
泛型类在实例化时必须指出具体的类型,也就是向类型参数传值,格式为:
className variable<dataType1, dataType2> = new className<dataType1, dataType2>();
注意:
泛型是 Java 1.5 的新增特性,它以C++模板为参照,本质是参数化类型(Parameterized Type)的应用。
类型参数只能用来表示引用类型,不能用来表示基本类型,如 int、double、char 等。但是传递基本类型不会报错,因为它们会自动装箱成对应的包装类。
三、Java接口(interface)
接口(Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。
接口就是比“抽象类"还“抽象”的“抽象类”, 可以更加规范的对子类进行约束。全面地专业地实现了:规范和具体实现的分离。接口是完全面向规范的,规定了一批类具有的公共方法规范。
从接口的实现者角度看,接口定义了可以向外部提供的服务。
从接口的调用者角度看,接口定义了实现者能提供那些服务。
接口是两个模块之间通信的标准,通信的规范。如果能把你要设计的模块之间的接口定义好,就相当于完成了系统的设计大纲,剩下的就是添砖加瓦的具体实现了。大家在工作以后,做系统时往往就是使用“面向接口”的思想来设计系统。
接口和实现类不是父子关系,是实现规则的关系。
接口主要特点:
1、接口不能实例化对象 可以用来声明引用变量的类型
2、一个类实现一个接口,那么必须实现这个接口中定义的所有方法,并且只能是public
3、jdk1.7以及之前 接口中只能有方法的定义 jdk1.8之后可以有default(虚拟扩展方法)和静态方法
抽象类和接口的区别
接口 |
抽象类 |
不考虑java8中default方法的情况下,接口中是没有实现代码的实现 |
抽象类中可以有普通成员方法 ,并且可以定义变量 |
接口中的方法修饰符号 只能是public |
抽象类中的抽象方法可以有public,protected,default |
接口中没有构造方法 |
可以有构造方法 |
接口和抽象类如何选择:
1、当我们需要一组规范的方法的时候,我们就可以用接口,在具体的业务中,来对接口进行实现,能达到以不变应对万变,多变的需求的情况我们只需要改变对应的实现类 。
2、如果多个实现类中有者相同可以复用的代码 这个时候就可以在实现类和接口之间,添加一个抽象类,把公共的代码抽出在抽象类中。然后要求不同实现过程的 子类可以重写抽象类中的方法,来完成各自的业务。
接口的定义(声明)语法格式如下:
[访问修饰符号] interface 接口名称 [extends 父接口1[,父接口2, ...]] {
常量定义:
方法定义:
}
访问修饰符:只能是public 或者默认
extends:接口支持多继承
接口中的属性只能是常量,默认就是public static finnal
接口中的方法默认是public abstract
换句话说,接口有以下特性:
接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键子。
接口中的方法都是公有的。
一个接口,可以拥有N个直接的父接口,如:
interface A
{
…
}
interface B
{
…
}
interface C
{
…
}
//一个接口,可以拥有N个直接的父接口
public interface D extends A,B,C
{
…
}
接口的实现
当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。
使用implements关键字实现接口,接口实现的语法:
类修饰符 class 类名称 implements 接口名称1[, 接口名称2, ...] {
...
}
下面给出一个例子,代码如下:
//用interface声明两个接口
interface Person {
void say();
}
interface Parent {
void work();
}
//用implements实现两个接口
class Child implements Person, Parent {
public void work() {
System.out.println("学习");
}
public void say() {
System.out.println("Child");
}
}
public class TestImplements{
public static void main(String[] args) {
Child c = new Child();
c.say();
c.work();
}
}
运行输出结果:
Child
学习
下面再给出一个例子,代码如下:
//创建一个接口名字为Abc
interface Abc {
//创建接口方法getMax
public int getMax();
//创建接口方法getMes
public String getMes();
}
//test2类描述的是实现接口的方法
class test2 implements Abc{
//实现接口里的方法
public int getMax()
{
//定义int类型的私有变量i
int i = 123;
//将变量i返回出去并退出方法
return i;
}
//实现接口里的方法
public String getMes()
{
//定义String类型的私有变量s
String s = "实现接口里的方法";
//将变量s返回出去并退出方法
return s;
}
}
public class TestImplements2{
//main方法为Java程序的入口方法
public static void main(String args[])
{
//创建test类的对象实例,引用为t
test2 t = new test2();
//实现了接口里的方法并进行调用
int i = t.getMax();
String s = t.getMes();
//打印并显示结果
System.out.println(i);
System.out.println(s);
}
}
运行输出结果:
123
实现接口里的方法