JavaSE 10 面向对象的高级特性,javase面向对象
JavaSE 10 面向对象的高级特性,javase面向对象
1、抽象
⑴ 概念
往往将具有抽象行为的父类设计成抽象类。不需要创建该类的对象,里面的某些行为不好描述。
⑵ abstract关键字
可以修饰类和方法,但不能修饰属性、局部变量、构造器和初始化块。
⑶ 特点
① 抽象类的特点
⒈ 抽象类中可以有普通的成员,包括普通属性和普通方法。
⒉ 抽象类不能创建本类的对象。
⒊ 抽象类有构造器(所有的类都有构造器)。抽象类的构造器的作用是为了被子类调用,而不是为了创建本类对象。
⒋ 抽象类不可以被private、static、final修饰。
⒌ 抽象类中可以没有抽象方法,但是很少这样做。
⒍ 子类继承抽象父类,必须重写抽象父类中的抽象方法。但是抽象子类继承抽象父类,则不需要重写抽象父类中的抽象方法。
⒎ 抽象类的本质,就是为了派生子类。
② 抽象方法的特点
⒈ 抽象方法不能放在实现类中,它只能放在抽象类中。
⒉ 抽象方法不能用private、static、final修饰,因为这会和方法的重写相冲突。
⒊ 抽象方法只能有方法的签名,不能有方法体,并用分号结尾。
⒋ 抽象方法的本质,就是为了让子类重写。
2、模版方法设计模式
模版父类的功能:⑴ 已经提供具体实现的方法,可以让子类继承并使用;⑵ 抽象方法,可以让子类重写。
好处就是提高代码的重用性。
示例:查看某段代码的执行总耗时
public class Test {
public static void main(String[] args) {
CodeClass cc = new CodeClass();
cc.calcTime();
}
}
abstract class CalcTime {
protected final void calcTime() { // 不需要子类重写,只需子类继承
long begin = System.currentTimeMillis(); // 计算代码执行前的时间
code(); // 执行抽象方法【需要子类实现】
long end = System.currentTimeMillis(); // 计算代码执行后的时间
System.out.println("总耗时:" + (end - begin) + "ms"); // 计算总耗时
}
protected abstract void code();
}
class CodeClass extends CalcTime {
@Override
protected void code() { // 子类重写抽象方法【需要测试耗时的代码】
String str = "Hi";
for (int i = 0; i < 5000; i++) {
str += "Java";
}
}
}
3、接口
⑴ 概念
接口就是一个特殊的“抽象类”,它比抽象类的层级更靠上,里面都是抽象方法。
接口 全是抽象方法
抽象类 一部分方法实现了,一部分方法没有实现
实现类 所有的方法都实现了
⑵ 好处
① 它避免了Java单继承的局限性。
② 接口的使用更加地灵活,它从逻辑上来说,不像继承需要 …是… ;它只需 …像… 即可。
③ 它提高了解耦性,降低了类和类之间的依赖性。
⑶ 特点
① 定义特点
⒈ 使用interface关键字来定义。
⒉ 抽象类中没有普通成员,只有抽象方法和常量。
⒊ 接口中的方法的修饰符只能是public abstract,换成其它的会报错,可以省略;常量中的修饰符只能是public static final,换成其它的会报错,可以省略。
示例:
【抽象方法】public abstract void method(); 或 void method();
【常量】public static final String COUNTRY = “中国”; 或 String COUNTRY = “中国”;
⒋ 接口中不能创建本类的对象。
⒌ 接口没有构造器(接口不属于类)。
⒍ 接口只能用public 或 默认访问修饰符修饰。
② 扩展特点
⒈ 通过implements关键字来实现。
⒉ 一个类可以实现多个接口,每个接口之间用逗号(,)隔开。
示例:class 类名 implements 接口1, 接口2, … { }
⒊ 一个类可以同时继承其他类,又可以实现多个接口。
示例:class 类名 extends 父类名 implements 接口1, 接口2, … { }
⒋ 一个接口可以继承多个接口,每个接口之间用逗号(,)隔开。
示例:interface 接口名 extends 接口名1, 接口名2, … { }
⒌ 总结:㈠ 类和类之间是单继承;㈡ 接口和接口之间是多继承。㈢ 类和接口之间是多实现;
⑷ 应用
接口和父类,在应用上是一致的,既可以用在多态参数上,也可以用在多态数组上。
① 多态参数
方法:
访问修饰符 返回值类型 方法名 (接口名 形参名){
形参名.方法( );
}
调用:方法名(new 实现类( ));
② 多态数组
接口名[] 数组名 = new 接口名[数组长度];
数组名[下标] = new 实现类( );
⑸ 用法总结
① 通过接口可以实现不相关类的相同行为,而不需要考虑这些类之间的层次关系。
② 通过接口可以指明多个类需要实现的方法,一般用于定义对象的扩张功能。
③ 接口主要用来定义规范。解除耦合关系。
4、接口和抽象方法的对比
接口(interface) 抽象类(abstract class)
有无构造器 没有 有
能否被实例化 不能 不能
有无普通成员 没有 有
抽象方法的修饰符 【只能是】public abstract 【除了private、static、final外】abstract
相同点:⑴ 都含有抽象方法。
⑵ 都是为了派生子类。
⑶ 都可以应用在多态上。
⑷ 如果是抽象子类继承抽象父类,或抽象类实现接口,则抽象类(抽象子类)不需要实现父类或接口中的方法(抽象)。
5、接口和抽象类之间的关系
6、工厂方法(FactoryMethod)
⑴ 概念
定义一个用于创建对象的接口(工厂),让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。
在面向对象的编程中,对象的创建工作简单,但是对象的创建时机却很重要。工厂方法可以将所要创建的具体对象的创建工作延迟到了子类,从而提供了一种扩展的策略,较好的解决了这种紧耦合的关系。
⑵ 示例
public class Test {
public static void main(String[] args) {
PhoneFactory pf = new MeizuFactory();
pf.makePhone().tel(); // 用魅族手机打电话
pf = new MiFactory();
pf.makePhone().tel(); // 用小米手机打电话
}
}
/**
* 生产手机的厂商
*/
interface PhoneFactory {
Phone makePhone(); // 生产手机的方法
}
/**
* 手机规范
*/
interface Phone {
void tel(); // 每个手机都能打电话
}
/**
* 魅族厂商
*/
class MeizuFactory implements PhoneFactory {
@Override
public Phone makePhone() {
return new Meizu();
}
}
/**
* 魅族手机
*/
class Meizu implements Phone {
@Override
public void tel() {
System.out.println("用魅族手机打电话");
}
}
/**
* 小米厂商
*/
class MiFactory implements PhoneFactory {
@Override
public Phone makePhone() {
return new Mi();
}
}
/**
* 小米手机
*/
class Mi implements Phone {
@Override
public void tel() {
System.out.println("用小米手机打电话");
}
}
7、代理模式(Proxy)
⑴ 理解
多一个代理类出来,用以代替原对象进行一些操作。例如租房子找中介、打官司请律师等。
⑵ 示例
public class Test {
public static void main(String[] args) {
Apple apple = new Apple(); // 委托人
Fushikang fushikang = new Fushikang(apple); // 代理人
fushikang.makePhone(); // 苹果公司生产iPhone
}
}
/**
* 生产手机的厂商
*/
interface PhoneFactory {
void makePhone(); // 生产手机的方法
}
/**
* 苹果公司【委托人】
*/
class Apple implements PhoneFactory {
@Override
public void makePhone() {
System.out.println("苹果公司生产iPhone");
}
}
/**
* 富士康【代理人】
*/
class Fushikang implements PhoneFactory {
PhoneFactory pf;
public Fushikang(PhoneFactory pf) { // 需要通过构造器来创建委托人对象
this.pf = pf;
}
@Override
public void makePhone() {
pf.makePhone(); // 调用委托人的方法
}
}
8、内部类
⑴ 概念
一个类中又可以完整的嵌套另外一个类。前者类称为外部类,后者被嵌套的类称为内部类。其它的类称为外部其它类。
内部类隶属于外部类或某个方法或某个代码块,但是本质上它是一个类。
编译后的class文件名为:外部类名$内部类名.class
class Outer { // 外部类
class Inner { // 内部类
}
}
class Other { // 外部其它类
}
⑵ 分类
① 定义在类体中
其地位等同于其他类的成员(成员变量等)。
⒈ 没有用static修饰:成员内部类
成员内部类的特点:
㈠ 可以加访问修饰符。
㈡ 可以有属性,方法,构造器,初始化块和内部类。
㈢ 可以直接访问外部类的私有成员。
㈣ 当外部类的属性名和内部类的属性名重名时,默认会访问内部类的属性,遵循就近原则。如果想访问外部类的同名属性,则需用外部类名.this.属性名 来调用。
㈤ 不允许定义静态成员(包括静态属性、静态方法、静态初始化块和静态内部类)。这是因为类的加载顺序为:加载外部类 > 加载静态成员(外部类的和内部类的) > 加载内部类。
㈥ 互访原则:1) 内部类可以直接访问外部类的所有成员。2) 外部类或外部其它类可以通过创建内部类的对象,来调用内部类的成员。
外部类创建内部类的对象的语法为:内部类名 名 = new 内部类名( );
外部其它类创建内部类的对象的语法为:外部类名.内部类名 名 = new 外部类名( ).new 内部类名( );
示例:
public class Test {
public static void main(String[] args) {
Outer.Inner oi = new Outer().new Inner(); // 外部其它类创建内部类的对象
oi.method();
Outer outer = new Outer();
outer.function();
}
}
class Outer {
class Inner {
private String name = "张三";
public void method() {
System.out.println(name);
}
}
void function() {
Inner inner = new Inner(); // 外部类创建内部类的成员
inner.method();
}
}
⒉ 使用static修饰:静态内部类
静态内部类的特点:
㈠ 可以加访问修饰符。
㈡ 可以有属性,方法,构造器,初始化块和内部类。
㈢ 可以直接访问外部类的私有成员。
㈣ 当外部类的属性名和内部类的属性名重名时,默认会访问内部类的属性,遵循就近原则。如果想访问外部类的同名属性,则需用外部类名.this.属性名 来调用。
㈤ 静态内部类可以定义静态成员(包括静态属性、静态方法、静态初始化块和静态内部类)。
㈥ 互访原则:1) 内部类可以直接访问外部类的所有成员。2) 外部类或外部其它类可以通过创建内部类的对象,来调用内部类的普通或静态成员;也可以通过内部类.静态成员名 的方式,来调用内部类的静态成员。
外部类创建内部类的对象的语法为:内部类名 名 = new 内部类名( );
外部其它类创建内部类的对象的语法为:外部类名.内部类名 名 = new 外部类名( ).new 内部类名( );
通过内部类名.成员的方式来调用内部类的静态成员的语法:内部类名.静态成员名;
示例:
public class Test {
public static void main(String[] args) {
Outer.Inner.method();
Outer outer = new Outer();
outer.function();
}
}
class Outer {
static class Inner {
private static String name = "张三";
public static void method() {
System.out.println(name);
}
}
void function() {
System.out.println(Inner.name); // 通过类名.属性名来调用 name
}
}
② 定义在方法或代码块中
其地位等同于方法中的局部变量。
⒊ 有类名的:局部内部类
局部内部类的特点:
㈠ 不能加访问修饰符(可以联想到局部变量不能被访问修饰符修饰)。最根本的原因就是局部变量的访问就是被限制在方法体或代码块中,用不着访问修饰符。
㈡ 可以有属性、方法、构造器、初始化块和内部类。
㈢ 可以直接访问外部类中的成员。
㈣ 不能定义静态成员(可以联想到局部变量不能被static修饰)。最根本的原因还是由于类的加载顺序,所有的静态成员需要在创建对象前,就都被加载到内存中【此时对象都没有,方法怎么可能被调用】。
㈤ 局部内部类的作用范围仅仅作用在定义它的方法或代码块中。而其他地方,例如其他方法或其他类中都不可以访问。
㈥ 当外部类的属性名和内部类的属性名重名时,默认会访问内部类的属性,遵循就近原则。如果想访问外部类的同名属性,则需用外部类名.this.属性名 来调用。
㈦ 因为局部变量的声明周期要小于局部内部类的声明周期,所以局部内部类只能访问用final修饰的局部变量。
示例:
public class Test {
public static void main(String[] args) {
new Outer().method();
}
}
class Outer {
String name = "张三";
void method() {
final String name = "李四";
class Inner {
void function() {
System.out.println(name); // 【就近原则,会访问局部变量(李四),如果不加final,则会报错Cannot refer to the non-final local variable name defined in an enclosing scope
}
}
new Inner().function();
System.out.println(Outer.this.name); // 通过外部类名.this.属性名,来访问外部类的成员变量(张三)
}
}
⒋ 没有类名的:匿名内部类
一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
语法:
new 父类构造器(实参列表) | 实现接口( ){
// 匿名内部类的类体
};
对比:
new 类名( ); 创建某类的对象
new 类名( ){ }; 创建某类的子类或对象
匿名内部类的特点:
㈠ 不能加访问修饰符。
㈡ 匿名内部类中的成员不包括构造器。
㈢ 可以直接访问外部类的所有成员。
㈣ 当外部类的属性名和内部类的属性名重名时,默认会访问内部类的属性,遵循就近原则。如果想访问外部类的同名属性,则需用外部类名.this.属性名 来调用。
㈤ 匿名内部类的作用范围仅仅作用在定义它的方法或代码块中。而其他地方,例如其他方法或其他类中都不可以访问。
㈥ 因为局部变量的声明周期要小于匿名内部类的声明周期,所以匿名内部类只能访问用final修饰的局部变量。
㈦ 匿名内部类的应用:一般用在方法的传参,可以简化代码。
示例:
public class Test {
public static void main(String[] args) {
new Outer().method();
Test t = new Test();
t.getSeed(new Flower(){ // 这里传入一个匿名内部类
@Override
public void makeSeed() {
System.out.println("得到向日葵的种子");
}
});
}
private void getSeed(Flower f) {
f.makeSeed();
}
}
interface Flower {
void makeSeed();
}
abstract class Person {
public abstract void show();
}
class Outer {
void method() {
final String name = "张三"; // 匿名内部类只能访问用final修饰的局部变量
new Person(){
@Override
public void show() {
System.out.println(name);
}
}.show(); // 通过匿名内部类,来调用类中的show 方法 【类似于 new Person().show(); 不同于普通的实现类,因为Person是一个抽象类,里面需要重写抽象方法】
}
}
9、枚举
⑴ 特点
在枚举类中,定义一组限定的值。例如一年有四季等。其类似于单例模式(类中只有一个值),而枚举则是有一组值。
⑵ JDK5.0之前 自定义枚举类
步骤:① 私有化构造器
② 创建一组公共的、静态的对象
示例:
class Season {
private Season(){ } // 私有化构造器
// 一组公共的、静态的对象
public static final Season SPRING = new Season();
public static final Season SUMMER = new Season();
public static final Season AUTUMN = new Season();
public static final Season WINTER = new Season();
}
⑶ JDK5.0之后 使用关键字定义枚举类
语法:① 使用enum关键字,来定义枚举类
② 对象必须放在首行,每个对象之间用逗号(,)隔开,最后用分号结尾(;)
语法为: 对象名(实参列表), 对象名(实参列表), … 对象名(实参列表);
示例:
enum Season {
SPRING(), SUMMER(), AUTUMN(), WINTER();
}
特点:① 系统默认提供私有的无参构造器
② 还可以定义其他的属性和方法
③ 使用enum关键字定义的枚举类都默认继承了Enum类。
⑷ Enum类的相关方法
toString( )方法
重写了Object类中的toString方法,它返回枚举常量的名称。即定义时的对象名称。
name( )方法
和toString方法的返回值相同,都是返回此枚举常量的名称。不过一般使用toString较多。
valueOf(String name)方法
这是一个静态方法。
将字符串转换为枚举对象,要求字符串必须为枚举类中定义过的枚举常量的名称之一。如果不存在,会报错:java.lang.IllegalArgumentException
values( )方法
这是一个静态方法。
返回当前枚举类中所有的定义过的对象。
ordinal( )方法
返回当前枚举类对象的位置号,位置号从0开始。
示例:
public class Test {
public static void main(String[] args) {
Season s1 = Season.SPRING;
System.out.println(s1); // 季节名:春天 描述:春暖花开 【因为重写了toString 方法】
System.out.println(s1.name()); // SPRING
Season s2 = Season.valueOf("SUMMER");
System.out.println(s2); // 季节名:夏天 描述:炎炎盛夏
System.out.println();
Season[] seasons = Season.values(); // 得到一个Season类型的数组,数组元素为所有已经定义过的对象
for (int i = 0; i < seasons.length; i++) {
System.out.println(seasons[i]);
}
Season s3 = Season.WINTER;
System.out.println(s3.ordinal()); // 3 【从0开始】
}
}
enum Season {
SPRING("春天", "春暖花开"), SUMMER("夏天", "炎炎盛夏"), AUTUMN("秋天", "秋高气爽"), WINTER("冬天", "白雪皑皑");
private String name; // 季节名
private String desc; // 季节描述
private Season(String name, String desc) {
this.name = name;
this.desc = desc;
}
@Override
public String toString() {
return "季节名:" + name + "\t描述:" + desc;
}
}
⑸ 让枚举类实现接口
语法:
enum 枚举类名 implements 接口名1, 接口名2 {
// 实现接口的抽象方法
}
实现接口的抽象方法的方式:
① 直接在枚举类中实现抽象方法
② 在对象处实现抽象方法
语法:
对象名(){
// 实现接口的抽象方法
}
示例:
public class Test {
public static void main(String[] args) {
Gender man = Gender.MALE;
man.work(); // 男人工作
Gender woman = Gender.FEMALE;
woman.work(); // 人工作
}
}
interface Work {
void work();
}
enum Gender implements Work {
MALE(){
@Override
public void work() { // 在对象处实现抽象方法
System.out.println("男人工作");
}
}, FEMALE();
@Override
public void work() { // 直接在枚举类中实现抽象方法
System.out.println("人工作");
}
}
10、注解
⑴ 概念
注解又称为元数据,可以修饰类、属性、方法、构造器、包、参数和局部变量。其用于解释和说明被修饰的数据。
注解类似于注释,用于说明和解释被修饰的数据,但是和注释不同的是,它可以被编译和运行。
⑵ JDK内置的三种基本注解
① @Override 代表被修饰的方法为重写方法,如果重写的方法签名不符合语法规定,则报编译错误。它只能用于修饰方法。
② @Deprecated 代表被修饰的数据为过时的,提醒调用方可以选择更佳方案。
③ @SuppressWarnings 抑制编译警告
⑶ 自定义注解
语法
修饰符 @interface 注解名 {
类型 方法名();
}
注解的方法的注意事项
① 方法的类型为:八大基本数据类型、String、Class、枚举类型(用enum修饰的类),以及这些类型的数组类型
② 方法名如果为value,则调用注解时,可以省略 【即 @注解名(值)】。建议方法名为value
③ 可以给方法添加默认值,则调用注解时,可以不赋值。语法:类型 方法名( ) default 默认值;
注解调用的语法
在要被修饰的数据上方,添加注解
简单类型的注解 @注解名(方法名 = 值)
数组类型的注解 @注解名(方法名 = {值, 值})
⑷ 元注解
@Rentation
保留策略,指定注解可以保留多久
可选的属性保存在RetentionPolicy枚举类中
SOURCE:保留到源代码中
CLASS:保留到字节码文件(.class)中
RUNTIME:保留到运行时
@Target
指定注解可以修饰什么数据
可选的属性保存在ElementType枚举类中
TYPE 类、接口(包括注释类型)或枚举声明
FIELD 属性【字段声明(包括枚举常量)】
METHOD 方法
PARAMETER 参数
CONSTRUCTOR 构造方法
LOCAL_VARIABLE 局部变量
ANNOTATION_TYPE 注释
PACKAGE 包
@Documented
定义的注解能否在JavaDoc文档中显示
@Inherited
指示定义的注解被自动继承
示例:定义一个可以修饰类、属性和方法,可以显示在JavaDoc文档中,名叫MyAnnotation的注解。它有一个类型为String数组、名为name的方法,其中默认值为“张三”。它被保存到class文件中。
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@MyAnnotation
public class Test {
@MyAnnotation(name = { "赵柳" })
private int age;
@MyAnnotation(name = { "李四", "王五" })
public void method() {
}
}
@Documented
@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.CLASS)
@interface MyAnnotation {
String[] name() default { "张三" };
}
相关文章
- 暂无相关文章
用户点评