本章是面向对象系列的最后一个章节,内容难度较前几章有些许提升,继续加油吧。
目录
关键字:static
使用背景
当我们编写一个类时,其实就是在描述其对象的属性和行为,并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或在无论产生多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量 。
使用范围:在Java类中,可用static修饰属性、方法、代码块、内部类
使用static修饰属性:静态变量(或类变量)
属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
static修饰属性的其他说明:
① 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
② 静态变量的加载要早于对象的创建。
③ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区(类的信息加载、静态域、常量池)的静态域中
④调用关系 类变量 实例变量
类 yes no
对象 yes yes
静态属性举例:System.out; Math.PI;
public class StaticTest {
public static void main(String[] args) {
Chinese c1 = new Chinese();
c1.name = "姚明";
c1.age = 40;
Chinese c2 = new Chinese();
c2.name = "马龙";
c2.age = 30;
c1.nation = "CHN";
System.out.println(c2.nation); //CHN
}
}
class Chinese{
String name;
int age;
static String nation;
}
类变量和实例变量内存解析
使用static修饰方法:静态方法
① 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
②调用 静态方法 非静态方法
类 yes no
对象 yes yes
③ 静态方法中,只能调用静态的方法或属性(生命周期一致,注意静态变量的加载早于对象的创建)
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
static注意点:
在静态的方法内,不能使用this关键字、super关键字。因为不需要实例就可以访问static方法
关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
开发中,如何确定一个属性是否要声明为static的?
属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
类中的常量也常常声明为static
开发中,如何确定一个方法是否要声明为static的?
操作静态属性的方法,通常设置为static的
工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections(可以不造对象直接用)
代码示例
class CircleTest{
public static void main(String[] args) {
Circle c1 = new Circle();
Circle c2 = new Circle();
System.out.println("c1的id:"+c1.getId());//1001
System.out.println("c2的id:"+c2.getId());//1002
System.out.println("创建的圆的个数"+Circle.getTotal());
}
}
public class Circle {
private double radius;
private int id;
private static int total;
private static int init = 1001;//声明的属性被多个对象共享
public Circle() {
id = init++;
total++;
}
public Circle(double radius) {
this();
// id = init++;
// total++;
this.radius = radius;
}
public double findArea() {
return 3.14*radius*radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public int getId() {
return id;
}
public static int getTotal() {
return total;
}
}
练习:编写一个类实现银行账户的概念,包含的属性有“帐号”、密码”、“存款余额”、“利率”、最小余额,定义封装这些属性的方法。 账号要自动生成。编写主类,使用银行账户类,输入、输出3个储户的上述信息。考虑:哪些属性可以设计成static属性。
public class AccountTest {
public static void main(String[] args) {
Account acct1 = new Account();
Account acct2 = new Account("12345", 900);
}
}
class Account{
private int id;
private String pwd = "000000";
private double balance;
private static double rate;
private static double minMoney = 1.0;
private static int init = 1001;//用于自动生成id,init被多个对象共用
public Account(){
id = init++;
}
public Account(String pwd,double balance) {
this();
this.pwd = pwd;
this.balance = balance;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public static double getRate() {
return rate;
}
public static void setRate(double rate) {
Account.rate = rate;
}
public static double getMinMoney() {
return minMoney;
}
public static void setMinMoney(double minMoney) {
Account.minMoney = minMoney;
}
public double getBalance() {
return balance;
}
}
单例 (Singleton)设计模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、 编程风格、以及解决问题的思考方式 。设计模式免去我们自己再思考和摸索 。就像是经典的棋谱,不同的棋局我们用不同的棋谱。 ”套路 ”
了解:有23中设计模式,可参考书籍大话设计模式
单例模式说明
所谓类的单例设计模式,就是采取一定方法保证在整个软件系统中,对某个类只能存在一个对象实例 ,并且该类只提供一个取得其对象实例的方法。 如果我们要让类在一个虚拟机中只能产生一个对象,首先必须将类的构造器的访问权限设置为private,这样就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象, 静态方法只能访问类中的静态成员变量,所以,指向类内部产生该类对象的变量也必须定义成静态的
单例模式的饿汉式实现
//单例模式的饿汉式实现
public class SingletonTest1 {
public static void main(String[] args) {
// Bank bank1 = new Bank(); //要求构造器私有化,使外部无法创建对象
// Bank bank2 = new Bank();
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);
}
}
//饿汉式
class Bank{
//1.私有化类的构造器
private Bank(){
}
//2.内部创建类的对象
//4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
//3.提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return instance;
}
}
单例模式的懒汉式实现
public class SingletonTest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2);
}
}
class Order{
//1.私有化类的构造器
private Order(){
}
//2.声明当前类对象,没有初始化
//4.此对象也必须声明为static的
private static Order instance = null;
//3.声明public、static的返回当前类对象的方法
public static Order getInstance(){
if(instance == null){ //若创建过对象,则不再创建
instance = new Order();
}
return instance;
}
}
区分饿汉式和懒汉式
饿汉式:
坏处:对象加载时间过长。
好处:饿汉式是线程安全的
懒汉式:好处:延迟对象的创建。
目前的写法坏处:线程不安全。--->到多线程内容时,在进行优化
单例模式的优点:
由于单例模式只生成一个实例,减少了系统性能开销 ,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
单例设计模式应用场景
理解main方法的语法
由于Java 虚拟机需要调用类的 main()方法,所以该方法的访问权限必须是 public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static,该方法接收一个 String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数 。
又因为main方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子 中多次碰到 。
main()方法的使用说明:
1. main()方法作为程序的入口
2. main()方法也是一个普通的静态方法
3. main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)
public class MainTest {
public static void main(String[] args) {//入口,arg参数默认string类型
Main.main(new String[100]);
MainTest test = new MainTest();//先造对象,再调方法
test.show();
}
public void show(){
}
}
class Main{
public static void main(String[] args) {//普通的静态main方法
for(int i = 0;i < args.length;i++){
args[i] = "args_" + i;
System.out.println(args[i]);
}
}
}
命令行参数用法举例
类的成员之四:代码块(或初始化块)
1. 代码块的作用:用来初始化类、对象
2. 代码块如果有修饰的话,只能使用static.
3. 分类:静态代码块 vs 非静态代码块
4. 静态代码块
内部可以有输出语句
随着类的加载而执行,而且只执行一次
作用:初始化类的信息
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
静态代码块的执行要优先于非静态代码块的执行
静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
5. 非静态代码块
内部可以有输出语句
随着对象的创建而执行,且先于构造器执行
每创建一个对象,就执行一次非静态代码块
作用:可以在创建对象时,对对象的属性等进行初始化
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
public class BlockTest {
public static void main(String[] args) {
String desc = Person.desc;
System.out.println(desc);
Person p1 = new Person();
Person p2 = new Person();
System.out.println(p1.age);
Person.info();
}
}
class Person{
//属性
String name;
int age;
static String desc = "我是一个人";
//构造器
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//非static的代码块
{
System.out.println("hello, block - 2");
}
{
System.out.println("hello, block - 1");
//调用非静态结构
age = 1;
eat();
//调用静态结构
desc = "我是一个爱学习的人1";
info();
}
//static的代码块
static{
System.out.println("hello,static block-2");
}
static{
System.out.println("hello,static block-1");
//调用静态结构
desc = "我是一个爱学习的人";
info();
//不可以调用非静态结构
// eat();
// name = "Tom";
}
//方法
public void eat(){
System.out.println("吃饭");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public static void info(){
System.out.println("我是一个快乐的人!");
}
}
输出:
******类加载时**********
hello,static block-2
hello,static block-1
我是一个快乐的人!
我是一个爱学习的人
******创建对象前*********
hello, block - 2
hello, block - 1
吃饭
我是一个快乐的人!
******再次创建对象******
hello, block - 2
hello, block - 1
吃饭
我是一个快乐的人!
1
我是一个快乐的人!
程序中成员变量的执行顺序
声明成员变量的默认初始化
显示初始化、多个初始化块(先于构造器)依次被执行(同级别下按先后顺序)
构造器再对成员进行初始化操作
通过对象.属性或对象.方法的方式,可多次给属性赋值
练习:执行顺序:由父即子,静态先行
//执行顺序:由父即子,静态先行
class Root{
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root(){
super();
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid(){
super();
System.out.println("Mid的无参数的构造器");
}
public Mid(String msg){
//通过this调用同一类中重载的构造器
this();
System.out.println("Mid的带参数构造器,其参数值:"
+ msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf的静态初始化块");
}
{
System.out.println("Leaf的普通初始化块");
}
public Leaf(){
//通过super调用父类中有一个字符串参数的构造器
super("尚硅谷");
System.out.println("Leaf的构造器");
}
}
public class LeafTest{
public static void main(String[] args){
new Leaf();
System.out.println();
new Leaf();
}
}
输出:
Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
//静态方法只加载一次
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
练习
class Father {
static {
System.out.println("11111111111");
}
{
System.out.println("22222222222");
}
public Father() {
System.out.println("33333333333");
}
}
public class Son extends Father {
static {
System.out.println("44444444444");
}
{
System.out.println("55555555555");
}
public Son() {
System.out.println("66666666666");
}
public static void main(String[] args) { // 由父及子 静态先行
System.out.println("77777777777");
System.out.println("************************");
new Son();
System.out.println("************************");
new Son();
System.out.println("************************");
new Father();
}
}
输出:
11111111111
44444444444
77777777777
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
main方法作为入口也是静态方法,通过类调用,需要等类先加载
关键字:final
1. final可以用来修饰的结构:类、方法、变量
2. final用来修饰一个类:此类不能被其他类所继承。
比如:String类、System类、StringBuffer类
3. final用来修饰方法:表明此方法不可以被重写
比如:Object类中getClass();
4. final用来修饰变量(成员变量或局部变量):此时的"变量"就称为是一个常量。名称大写,且只能被赋值一次。
final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化。(不能用方法来赋值,因为方法调用晚)
final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
static final 用来修饰属性:全局常量
public class FinalTest {
// final int WIDTH; 不能默认初始化
final int WIDTH = 0;
final int LEFT;
final int RIGHT;
final int DOWN;
{
LEFT = 1; //可在代码块中初始化
}
public FinalTest(){
RIGHT = 2; //可在构造器中初始化
}
public FinalTest(int n){
RIGHT = n; //可在构造器中初始化
}
// public void setDown(int down){//不能用方法对final的属性赋值。方法调用的晚
// this.DOWN = down;
// }
public void doWidth(){
// width = 20;不能修改
}
public void show(){
final int NUM = 10;//表示常量
// NUM += 20;//不能修改
}
public void show(final int num){
//调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
// num = 20;//编译不通过
System.out.println(num);
}
public static void main(String[] args) {
int num = 10;//普通变量
num = num + 5;
FinalTest test = new FinalTest();
// test.setDown(3);
test.show(10);
}
}
final class FinalA{
}
//class B extends FinalA{ 无法继承FinalA
}
//class C extends String{ 无法继承String
}
class AA{
public final void show(){
}
}
class BB extends AA{
//public void show(){ 无法重写final修饰的方法
}
}
抽象类与抽象方法
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类 。
abstract关键字的使用
1.abstract:抽象的
2.abstract可以用来修饰的结构:类、方法
3. abstract修饰类:抽象类
抽象类不能实例化
抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
4. abstract修饰方法:抽象方法
抽象方法只有方法的声明,没有方法的实现。以分号结束。比如:public abstract void talk();
包含抽象方法的类必须被声明为抽象类。反之,抽象类中可以没有抽象方法。
若子类重写了父类中的所有的抽象方法后,此子类方可实例化
若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
public class AbstractTest {
public static void main(String[] args) {
//一旦Person类抽象了,就不可实例化
// Person p1 = new Person();
// p1.eat();
}
}
abstract class Creature{
public abstract void breath();
}
abstract class Person extends Creature{
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//不是抽象方法:
public void eat(){
}
//抽象方法
public abstract void eat();
public void walk(){
System.out.println("人走路");
}
}
class Student extends Person{
public Student(String name,int age){
super(name,age);
}
public Student(){
}
public void eat(){
System.out.println("学生多吃有营养的食物");
}
@Override
public void breath() {
System.out.println("学生应该呼吸新鲜的没有雾霾的空气");
}
}
抽象的应用场景举例
抽象类是用来模型化那些父类无法确定全部实现,而由其子类提供具体实现的对象的类。
abstract使用上的注意点:
1.abstract不能用来修饰:属性、构造器等结构
2.abstract不能用来修饰私有方法、静态方法、final的方法、final的类
练习
编写一个Employee类,声明为抽象类,包含如下三个属性: name,id,salary。提供必要的构造器和抽象方法: work()。
对于 Manager类来说,他既是员工,还具有奖金 (bonus)的属性。请使用继承的思想,设计 CommonEmployee类和 Manager类 ,要求类中提供必要的方法进行属性访问。
代码略
创建抽象类的匿名子类
public class PersonTest {
public static void main(String[] args) {
method(new Student());//匿名对象
Worker worker = new Worker();
method1(worker);//非匿名的类,非匿名的对象
method1(new Worker());//非匿名的类,匿名的对象
//Person是抽象类,不能实例化
//创建了一匿名子类的对象:p
Person p = new Person(){
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸");
}
};
method1(p);
//创建匿名子类的匿名对象
method1(new Person(){//多态性体现
@Override
public void eat() {
System.out.println("吃好吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸新鲜空气");
}
});
}
public static void method1(Person p){
p.eat();
p.breath();
}
public static void method(Student s){
}
}
//Student和person类在上述代码定义过,此处略
class Worker extends Person{
@Override
public void eat() {
}
@Override
public void breath() {
}
}
模板方法设计模式(TemplateMethod )
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题 :
当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去 ,让子类实现 。
换句话说, 在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
实例代码一:
public class TemplateTest {
public static void main(String[] args) {
SubTemplate t = new SubTemplate();
t.spendTime();
}
}
abstract class Template{
//计算某段代码执行所需要花费的时间
public void spendTime(){
long start = System.currentTimeMillis();
this.code();//不确定的部分、易变的部分
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template{
@Override
public void code() {
//寻找质数
for(int i = 2;i <= 1000;i++){
boolean isFlag = true;
for(int j = 2;j <= Math.sqrt(i);j++){
if(i % j == 0){
isFlag = false;
break;
}
}
if(isFlag){
System.out.println(i);
}
}
}
}
实例代码二:
public class TemplateMethodTest {
public static void main(String[] args) {
BankTemplateMethod btm = new DrawMoney();
btm.process();
BankTemplateMethod btm2 = new ManageMoney();
btm2.process();
}
}
abstract class BankTemplateMethod {
// 具体方法
public void takeNumber() {
System.out.println("取号排队");
}
public abstract void transact(); // 办理具体的业务(钩子方法)
public void evaluate() {
System.out.println("反馈评分");
}
// 模板方法,把基本操作组合到一起,子类一般不能重写
public final void process() {
this.takeNumber();
this.transact();// 像个钩子,具体执行时,挂哪个子类,就执行哪个子类的实现代码
this.evaluate();
}
}
class DrawMoney extends BankTemplateMethod {
public void transact() {
System.out.println("我要取款!!!");
}
}
class ManageMoney extends BankTemplateMethod {
public void transact() {
System.out.println("我要理财!我这里有2000万美元!!");
}
}
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子 ,比如常见的有:
数据库访问的封装
Junit 单元测试
JavaWeb的Servlet中关于doGet/doPost方法调用
Hibernate中模板程序
Spring中JDBCTemlate JDBCTemlate 、HibernateTemplate
练习
参见实验三
接口(interface)
概述
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口 ,就可以得到多重继承的效果 。
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅是具有相同行为特征而已 。例如:鼠标、键盘打、打印机、 扫描仪移动硬盘等都支持 USB 连接 。
接口就是规范,定义的是一组规则。体现了现实世界中“如果你/要... 则必须能 ... ”的思想。 继承是一个"是不是"的关系,而接口实现则是"能不能"的关系。
接口的本质是契约,标准,规范 ,就像我们的法律一样。制定好后大家都要遵守 。
举例:
接口的使用
1.接口使用interface来定义
2.Java中,接口和类是并列的两个结构
3.接口的定义:定义接口中的成员
3.1 JDK7及以前:只能定义全局常量和抽象方法
>全局常量:public static final修饰。但是书写时,可以省略不写
>抽象方法:public abstract修饰的
3.2 JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)
4. 接口中不能定义构造器!意味着接口不可以实例化
5. Java开发中,接口通过让类去实现(implements)的方式来使用.
如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
6. Java类可以实现多个接口 --->弥补了Java单继承性的局限性
格式:class AA extends BB implements CC,DD,EE
7. 接口与接口之间可以继承,而且可以多继承
8. 接口的具体使用,体现多态性
9. 接口,实际上可以看做是一种规范
public class InterfaceTest {
public static void main(String[] args) {
System.out.println(Flyable.MAX_SPEED);
System.out.println(Flyable.MIN_SPEED);
// Flyable.MIN_SPEED = 2;//不能修改
Plane plane = new Plane();
plane.fly();
}
}
interface Flyable{
//全局常量
public static final int MAX_SPEED = 7900;//第一宇宙速度
int MIN_SPEED = 1;//省略了public static final
//抽象方法
public abstract void fly();
//省略了public abstract
void stop();
//Interfaces cannot have constructors
// public Flyable(){
//
// }
}
interface Attackable{
void attack();
}
class Plane implements Flyable{
@Override
public void fly() {
System.out.println("通过引擎起飞");
}
@Override
public void stop() {
System.out.println("驾驶员减速停止");
}
}
abstract class Kite implements Flyable{//没有覆盖接口中的所有抽象方法
@Override
public void fly() {
}
}
class Bullet extends Object implements Flyable,Attackable,CC{
@Override
public void attack() {
}
@Override
public void fly() {
}
@Override
public void stop() {
}
@Override
public void method1() {
}
@Override
public void method2() {
}
}
//接口的继承
interface AA{
void method1();
}
interface BB{
void method2();
}
interface CC extends AA,BB{
}
实例演示
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
//1.创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);
//2. 创建了接口的非匿名实现类的匿名对象
com.transferData(new Printer());
//3. 创建了接口的匿名实现类的非匿名对象
USB phone = new USB(){
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机结束工作");
}
};
com.transferData(phone);
//4. 创建了接口的匿名实现类的匿名对象
com.transferData(new USB(){
@Override
public void start() {
System.out.println("mp3开始工作");
}
@Override
public void stop() {
System.out.println("mp3结束工作");
}
});
}
}
class Computer{
public void transferData(USB usb){//USB usb = new Flash();多态的体现
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{
//常量:定义了长、宽、最大最小的传输速度等
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
代理模式(Proxy)
概述:
代理模式是Java开发中使用较多的一种设计模式。代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
为什么要用代理模式?
- 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
- 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
// server.browse();//不通过代理直接调用
ProxyServer proxyServer = new ProxyServer(server); //多态的体现
proxyServer.browse();
}
}
interface NetWork{
public void browse();
}
//被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器访问网络");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work){//传入待代理的对象
this.work = work;
}
public void check(){
System.out.println("联网之前的检查工作");
}
@Override
public void browse() {
check();
work.browse(); 执行被代理对象中的方法
}
}
示例
public class StaticProxyTest {
public static void main(String[] args) {
Proxy s = new Proxy(new RealStar());//多态的体现
s.confer();
s.signContract();
s.bookTicket();
s.sing();
s.collectMoney();
}
}
interface Star {
void confer();// 面谈
void signContract();// 签合同
void bookTicket();// 订票
void sing();// 唱歌
void collectMoney();// 收钱
}
//被代理类
class RealStar implements Star {
public void confer() {
}
public void signContract() {
}
public void bookTicket() {
}
public void sing() {
System.out.println("明星:歌唱~~~");
}
public void collectMoney() {
}
}
//代理类
class Proxy implements Star {
private Star real;
public Proxy(Star real) {
this.real = real;
}
public void confer() {
System.out.println("经纪人面谈");
}
public void signContract() {
System.out.println("经纪人签合同");
}
public void bookTicket() {
System.out.println("经纪人订票");
}
public void sing() {
real.sing();
}
public void collectMoney() {
System.out.println("经纪人收钱");
}
}
代理模式
工厂模式
详见后续拓展:工厂设计模式
练习
package com.atguigu.java1;
interface A {
int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX() {
//编译不通过。因为x是不明确的
// System.out.println(x);
System.out.println(super.x);//1
System.out.println(A.x);//0
}
public static void main(String[] args) {
new C().pX();
}
}
package com.sxt.p3;
interface CompareObject{
int compareTo(Object o);
}
public class Circle {
private int radius;
public Circle(int radius) {
super();
this.radius = radius;
}
public int getRadius() {
return radius;
}
public void setRadius(int radius) {
this.radius = radius;
}
}
class CompareCircle extends Circle implements CompareObject{
public CompareCircle(int radius) {
super(radius);
}
@Override
public int compareTo(Object o) {//如果定义的数据类型是Integer,可以通过类的方法直接比较
if(this == o) {
return 0
}
if (o instanceof CompareCircle) {
CompareCircle c = (CompareCircle)o;
if(this.getRadius() > c.getRadius()) {
return 1;
}
else if(this.getRadius() < c.getRadius()){
return -1;
}else
return 0;
正确的方式二:
return this.getRadius().compareTo(c.getRadius());
}else {
//抛出异常
throw new RuntimeException("传入的数据类型不匹配");
}
}
}
class InterfaceTest{
public static void main(String[] args) {
CompareCircle c1 = new CompareCircle(6);
CompareCircle c2 = new CompareCircle(6);
System.out.println(c1.compareTo(c2));
}
}
Java8中接口的新特性
Java 8,除了定义全局常量和抽象方法之外,还可以定义静态方法和默认方法。
静态方法: 使用 static 关键字修饰。 可以通过接口直接调用静态方法 ,并执行其方法体。
默认方法: 默认方法使用default 关键字修饰。 可以通过实现类对象来调用。
public interface CompareA {
//静态方法
public static void method1(){
System.out.println("CompareA:北京");
}
//默认方法
public default void method2(){
System.out.println("CompareA:上海");
}
default void method3(){
System.out.println("CompareA:上海");
}
}
知识点1:接口中定义的静态方法,只能通过接口来调用。
知识点2:通过实现类的对象,可以调用接口中的默认方法。
如果实现类重写了接口中的默认方法,调用时,调用的是重写以后的方法
知识点3:规定:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则
知识点4:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。-->接口冲突。这就需要我们必须在实现类中重写此方法
知识点5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
method3();//调用自己定义的重写的方法
super.method3();//调用的是父类中声明的
CompareA.super.method3();//调用接口中的默认方法
public class SubClassTest {
public static void main(String[] args) {
SubClass s = new SubClass();
// s.method1(); //无法调用此方法
// SubClass.method1();
//知识点1:接口中定义的静态方法,只能通过接口来调用。
CompareA.method1();
//知识点2:通过实现类的对象,可以调用接口中的默认方法。
//如果实现类重写了接口中的默认方法,调用时,调用的是重写以后的方法
s.method2();
//知识点3:规定:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
//那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则
//知识点4:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
//那么在实现类没有重写此方法的情况下,报错。-->接口冲突。
//这就需要我们必须在实现类中重写此方法
s.method3();
}
}
class SubClass (extends SuperClas)s implements CompareA,CompareB{(//如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,报错。-->接口冲突。)
public void method2(){
System.out.println("SubClass:上海");
}
public void method3(){
System.out.println("SubClass:深圳");
}
//知识点5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod(){
method3();//调用自己定义的重写的方法
super.method3();//调用的是父类中声明的
//调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}
}
class SuperClass {
public void method3(){
System.out.println("SuperClass:北京");
}
}
public interface CompareB {
default void method3(){
System.out.println("CompareB:上海");
}
}
在子类(或实现类)的方法中调用父类接口中被重写的方法。 父类.super.方法名
interface Filial {// 孝顺的
default void help() {
System.out.println("老妈,我来救你了");
}
}
interface Spoony {// 痴情的
default void help() {
System.out.println("媳妇,别怕,我来了");
}
}
class Father{
public void help(){
System.out.println("儿子,救我媳妇!");
}
}
class Man extends Father implements Filial, Spoony {
@Override
public void help() {
System.out.println("我该救谁呢?");
Filial.super.help();//救妈
Spoony.super.help();//救老婆
}
}
类的成员之五:内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整结构又只为外事物提供服务,那么整个内部的完整结构最好使用内部类 。1.Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类。内部类一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。内部类的名字不能与包含它的外部类类名相同
2.内部类的分类:成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)
3.成员内部类:
一方面,作为外部类的成员:
可以调用外部类的结构
可以被static修饰(对比:普通的类不用static修饰),但此时就不能再使用外层类的非static的成员变量
可以被4种不同的权限修饰
另一方面,作为一个类:
类内可以定义属性、方法、构造器等
可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
可以被abstract修饰
编译以后生成 OuterClass字节码文件 (也适用于局部内部类)
4.关注如下的3个问题
4.1 如何实例化成员内部类的对象
//创建DOG实例(静态的成员内部类)
Person.Dog dog = new Person.Dog();
dog.sing();
//创建Bird类实例(非静态的成员内部类)
//Person.Bird bird = new Person.Bird();//错误的
Person p = new Person();
Person.Bird bird = p.new Bird();
4.2 如何在成员内部类中区分调用外部类的结构
4.3 开发中局部内部类的使用,见程序代码InnerClassTest1.java
public class InnerClassTest {
public static void main(String[] args) {
//创建DOG实例(静态的成员内部类)
Person.Dog dog = new Person.Dog();
dog.sing();
//创建Bird类实例(非静态的成员内部类)
//Person.Bird bird = new Person.Bird();//错误的
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.sing();
bird.display("黄鹂");
}
}
class Person{
String name = "杜鹃";
int age;
public void eat() {
System.out.println("人:吃饭");
}
//静态成员内部类
static class Dog{
String name;
public Dog() {
}
public void sing() {
System.out.println("小小鸟");
//eat();//不能调用。静态内部类就不能再使用外层类的非static的成员或方法
}
}
//非静态成员内部类
class Bird{
String name = "麻雀";
public Bird() {
}
public void sing() {
System.out.println("小小鸟");
eat();//调用外部类的属性 完整写法:Person.this.eat()
}
public void display(String name) {
System.out.println(name);//方法的参数
System.out.println(this.name);//内部类的属性
System.out.println(Person.this.name);//外部类的属性
}
}
public void method() {
//局部内部类
class AA{
}
}
{
//局部内部类
class BB{
}
}
public Person() {
//局部内部类
class CC{
}
}
}
开发中局部内部类的使用实例:
只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类
但是它的对象可以通过外部方法返回值使用,返回值类型只能是局部内部类的父类或父接口类型
局部内部类在开发中很少见,多出现在下述情况:
局部内部类的特点:
内部类仍然是一个独立的,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号。
只能在声明它的方法或代码块中使用,而且是先后。除此之外任何地都不能使用该类。
局部内部类可以使用外部类的成员,包括私有的。
局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。
局部内部类和变量地位类似,不能使用public,protected, public,缺省 ,private
局部内部类不能使用static修饰,因此也不能包含静态成员
public void method(){
//局部内部类
class AA{
}
}
匿名内部类
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
格式:
new 父类构造器(实参列表)|实现接口 (){
//匿名内部类的类体部分
}
匿名内部类的特点
匿名内部类必须继承父类或实现接口
匿名内部类只能有一个对象
匿名内部类对象只能使用多态形式引用
public class InnerClassTest1 {
//返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){//希望调这个方法的时候返回这个接口的实现类的对象
//创建一个实现了Comparable接口的类:局部内部类
//方式一:有名实现类的匿名对象
class MyComparable implements Comparable{
@Override
public int compareTo(Object o) {
return 0;
}
}
return new MyComparable();
//方式二:匿名实现类的匿名对象
return new Comparable(){//接口是不能new,但此处比较特殊是子类对象实现接口,只不过没有为对象取名
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}
章节小结
至此,关于Java面向对象特性的内容全部结束。相信坚持下来的读者肯定会有很多收获。面向对象特性 ,是Java学习的核心、重头戏 。希望大家及时地梳理、总结。