- 1、static关键字
- 1.1、static 的使用(静态变量 方法)
- 1.2、类变量 vs 实例变量内存解析
- 1.3、自定义 ArrayUtil工具类的优化
- 1.4、static 的应用举例
- 1.5、练习
- 1.6、单例(Singleton)设计模式
- 2、理解 main 方法的语法(了解)
- 3、类的成员之四:代码块(初始化块)
- 4、final关键字
- 4.1、final的使用
- 4.2、练习
- 5、抽象类与抽象方法
- 5.1、abstract的使用
- 5.2、抽象类应用
- 5.3、抽象类的匿名子类
- 5.4、练习
- 5.5、多态的应用:模板方法设计模式
- 6、接口(interface)
- 6.1、概述
- 6.2、接口的使用
- 6.2、接口的举例
- 6.3、接口的应用:代理模式(Proxy)
- 6.4、接口的应用:工厂模式
- 6.5、练习
- 6.7、Java 8 中关于接口的改进
- 6.8、默认方法接口冲突问题
- 7、类的内部成员之五:内部类
- 7.1、成员内部类
- 7.1.1、静态内部类
- 7.1.2、非静态内部类
- 7.1.3、练习
- 7.3、局部内部类
- 7.4、匿名内部类
- 7.4.1、匿名内部类描述
- 7.4.2、练习
- 7.5、练习
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过 new 关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
1.1、static 的使用(静态变量 方法)-
1.static:静态的
-
2.static可以用来修饰:属性、方法、代码块、内部类
-
3.使用static修饰属性:静态变量(或类变量)
-
3.1 属性:按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
-
3.2 static修饰属性的其他说明
-
① 静态变量随着类的加载而加载。可以通过"**类.静态变量"**的方式进行调用
-
② 静态变量的加载要早于对象的创建。
-
③ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中
-
调用 类变量(静态变量) 实例变量 类 yes no 对象 yes yes
-
-
3.3 静态属性举例:System.out; Math.PI;(类.属性)
-
-
4.使用static修饰方法:静态方法
-
① 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
-
静态方法 非静态方法 类 yes no 对象 yes yes -
③ 静态方法中,只能调用静态的方法或属性
非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
-
-
5.static注意点:
- 5.1 在静态的方法内,不能使用this关键字、super关键字
- 5.2 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
-
6.开发中,如何确定一个属性是否要声明为static的?
-
属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
-
类中的常量也常常声明为static
开发中,如何确定一个方法是否要声明为static的?
-
操作静态属性的方法,通常设置为static的
-
工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections
-
-
使用静态变量或方法会随着类的加载而加载,随着类的消失而消失;非静态变量或方法会随着对象的加载而加载,随着对象的消失而消失
-
静态变量或方法可以用自己的类调用,也可以用自己所在类创建的对象调用
public class StaticTest {
public static void main(String[] args) {
Chinese.nation = "中国";
Chinese c1 = new Chinese();
c1.name = "姚明";
c1.age = 40;
c1.nation = "CHN";
Chinese c2 = new Chinese();
c2.name = "马龙";
c2.age = 30;
c2.nation = "CHINA";
System.out.println(c1.nation);
//编译不通过
// Chinese.name = "张继科";
c1.eat();
Chinese.show();
//编译不通过
// Chinese.eat();
// Chinese.info();
}
}
//中国人
class Chinese{
String name;
int age;
static String nation;
public void eat(){
System.out.println("中国人吃中餐");
//调用非静态结构
this.info();
System.out.println("name :" +name);
//调用静态结构
walk();
System.out.println("nation : " + nation);
}
public static void show(){
System.out.println("我是一个中国人!");
//不能调用非静态的结构
// eat();//this.eat()
// name = "Tom";
//可以调用静态的结构
System.out.println(Chinese.nation);//调用静态属性,Chinese.nation可以写成nation
walk();
}
public void info(){
System.out.println("name :" + name +",age : " + age);
}
public static void walk(){
}
}
1.2、类变量 vs 实例变量内存解析
1.3、自定义 ArrayUtil工具类的优化
将ArrayUtil工具类中的方法静态化,就不用在对象调用方法,直接通过类.方法调用
public class ArrayUtil {
// 求数组的最大值
public static int getMax(int[] arr) {
int maxValue = arr[0];
for (int i = 1; i < arr.length; i++) {
if (maxValue < arr[i]) {
maxValue = arr[i];
}
}
return maxValue;
}
// 求数组的最小值
public static int getMin(int[] arr) {
int minValue = arr[0];
for (int i = 1; i < arr.length; i++) {
if (minValue > arr[i]) {
minValue = arr[i];
}
}
return minValue;
}
// 求数组的总和
public static int getSum(int[] arr) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[i];
}
return sum;
}
// 求数组的平均值
public static int getAvg(int[] arr) {
return getSum(arr) / arr.length;
}
//如下的两个同名方法构成了重载
// 反转数组
public static void reverse(int[] arr) {
for (int i = 0; i < arr.length / 2; i++) {
int temp = arr[i];
arr[i] = arr[arr.length - i - 1];
arr[arr.length - i - 1] = temp;
}
}
// 复制数组
public static int[] copy(int[] arr) {
int[] arr1 = new int[arr.length];
for (int i = 0; i < arr1.length; i++) {
arr1[i] = arr[i];
}
return arr1;
}
// 数组排序
public static void sort(int[] arr) {
// 冒泡排序
for (int i = 0; i < arr.length - 1; i++) {
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// int temp = arr[j];
// arr[j] = arr[j + 1];
// arr[j + 1] = temp;
//错误的:
// swap(arr[j],arr[j + 1]);
//正确的:
swap(arr,j,j + 1);
}
}
}
}
//错误的:交换数组中指定两个位置元素的值
// public void swap(int i,int j){
// int temp = i;
// i = j;
// j = temp;
// }
//正确的:交换数组中指定两个位置元素的值
public static void swap(int[] arr,int i,int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
// 遍历数组
public static void print(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + "t");
}
System.out.println();
}
// 查找指定元素
public static int getIndex(int[] arr, int dest) {
// 线性查找:
for (int i = 0; i < arr.length; i++) {
if (dest == arr[i]) {
return i;
}
}
return -1;//返回一个负数,表示没有找到
}
}
public class ArrayUtilTest {
public static void main(String[] args) {
int[] arr = new int[]{32,34,32,5,3,54,654,-98,0,-53,5};
int max = ArrayUtil.getMax(arr);
System.out.println("最大值为:" + max);
System.out.println("排序前:");
ArrayUtil.print(arr);
ArrayUtil.sort(arr);
System.out.println("排序后:");
ArrayUtil.print(arr);
}
}
1.4、static 的应用举例
public class CircleTest {
public static void main(String[] args) {
Circle c1 = new Circle();
Circle c2 = new Circle();
Circle c3 = new Circle();
System.out.println("c1 的 ID:" + c1.getId());
System.out.println("c2 的 ID:" + c2.getId());
System.out.println("c3 的 ID:" + c3.getId());
System.out.println("创建圆的个数: " + Circle.getTotal());
}
}
class Circle{
private double radius;
private int id; //需要自动赋值
public Circle(){
id = init++;//每创建一个对象就自动加1
total++;
}
public Circle(double radius){
this();
//或
// id = init++;
// total++;
this.radius = radius;
}
private static int total;//记录创建圆的个数
private static int init = 1001;//static 声明的属性被所有对象所共享
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;
}
}
1.5、练习
【题目描述】
编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。 账号要自动生成。编写主类,使用银行账户类,输入、输出 3 个储户的上述信息。 考虑:哪些属性可以设计成 static 属性。
【代码实现】
public class Account {
private int id; //账号
private String pwd = "000000"; //密码
private double balance; //存款余额
private static double interestRate; //利率
private static double minMoney = 1.0; //最小余额
private static int init = 1001; //用于自动生成 id
public Account(){ //账号自动生成
id = init++;
}
public Account(String pwd,double balance){//对象有的属性才会在构造器中初始化,其他的静态属性都是类的事情,和构造器无关
id = init++;
this.pwd = pwd;
this.balance = balance;
}
//get set方法
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public static double getInterestRate() {
return interestRate;
}
public static void setInterestRate(double interestRate) {
Account.interestRate = interestRate;
}
public static double getMinMoney() {
return minMoney;
}
public static void setMinMoney(double minMoney) {
Account.minMoney = minMoney;
}
public int getId() {
return id;
}
public double getBalance() {
return balance;
}
@Override
public String toString() {
return "Account [id=" + id + ", pwd=" + pwd + ", balance=" + balance + "]";
}
}
public class AccountTest {
public static void main(String[] args) {
Account acct1 = new Account();
Account acct2 = new Account("qwerty",2000);
Account.setInterestRate(0.012); //通过静态方法设置,所有的账户都一样
Account.setMinMoney(100);
System.out.println(acct1);//调用toString()方法,如果重写了toString()方法就调用重写后的toString()方法,否则就调用Object中的toString()方法
System.out.println(acct2);
System.out.println(acct1.getInterestRate());
System.out.println(acct1.getMinMoney());
}
}
1.6、单例(Singleton)设计模式
饿汉式 VS 懒汉式
- 饿汉式:
- 好处:饿汉式是线程安全的
- 坏处:对象加载时间过长
- 懒汉式:
- 好处:延迟对象的创建。
- 目前的写法坏处:线程不安全。—>到多线程内容时,再修改
单例模式的饿汉式
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);//true
}
}
//饿汉式
class Bank{
//1.私有化类的构造器
private Bank(){
}
//2.内部创建类的对象
//要求此对象也必须声明为静态的
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.声明当前类对象,没有初始化。
//此对象也必须声明为 static 的
private static Order instance = null;
//3.声明 public、static 的返回当前类对象的方法
public static Order getInstance(){
if(instance == null){
instance = new Order();
}
return instance;
}
}
单例模式的优点
由于单例模式只生成一个实例,减少了系统性能开销(没有必要造多个对象),当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
单例(Singleton)设计模式-应用场景
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
- Application也是单例的典型应用
- Windows 的 **Task Manager (任务管理器)**就是很典型的单例模式
- Windows 的 **Recycle Bin(回收站)**也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
main()方法的使用说明
- 1.main()方法作为程序的入口
- 2.main()方法也是一个普通的静态方法
- 3.main()方法也可以作为我们与控制台交互的方式。(之前,使用 Scanner)
public class MainTest {
public static void main(String[] args) { //入口
Main.main(new String[100]);
MainTest test = new MainTest();
test.show();//非静态方法用对象调用
}
public void show(){//非静态方法
}
}
class Main{
public static void main(String[] args) {
args = new String[100];
for(int i = 0;i < args.length;i++){
args[i] = "args_" + i;
System.out.println(args[i]);
}
}
}
命令行参数用法举例
public class MainDemo {
public static void main(String[] args) {
for(int i = 0;i < args.length;i++){
System.out.println("*****" + args[i]);
int num = Integer.parseInt(args[i]);
System.out.println("#####" + num);
}
}
}
运行程序 MainDemo.java
javac MainDemo.java java MainDemo “87” “89” “66” //先javac生成字节码文件再将字节码文件解释运行,运行时添加参数
- 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("我是一个快乐的人!");
}
}
静态初始化块举例 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();
}
}
【运行结果】
静态初始化块举例 2
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();
}
}
【运行结果】
总结:程序中成员变量赋值的执行顺序
- ①默认初始化
- ②显式初始化 / ⑤在代码块中赋值
- ③构造器中初始化
- ④有了对象以后,可以通过"对象.属性"或"对象.方法"的方式,进行赋值。
//验证:②显式初始化 / ⑤在代码块中赋值
public class OrderTest {
public static void main(String[] args) {
Order order = new Order();
System.out.println(order.orderId);
}
}
class Order{
int orderId = 3;//显式初始化
{
orderId = 4;//在代码块中赋值
}
}
4、final关键字
4.1、final的使用
- 1.final可以用来修饰的结构:类、方法、变量
- 2.final 用来修饰一个类:此类不能被其他类所继承;比如:String类、System类、StringBuffer类
- 3.final 用来修饰方法:表明此方法不可以被重写;比如:Object类中getClass();
- 4.final 用来修饰变量:此时的"变量"就称为是一个常量
- 4.1 final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化(默认初始化,对象.属性/对象.方法不能赋值)
- 4.2 final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
static final 用来修饰属性:全局常量
public class FinalTest {
/
public class EmployeeTest {
public static void main(String[] args) {
//多态
Employee manager = new Manager("库克",1001,5000,50000);
manager.work();
CommonEmployee commonEmployee = new CommonEmployee();
commonEmployee.work();
}
}
练习2
【题目描述】
【代码实现】
public abstract class Employee {
private String name;
private int number;
private MyDate birthday;
public Employee(String name, int number, MyDate birthday) {
super();
this.name = name;
this.number = number;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
public abstract double earnings();//抽象方法
@Override
public String toString() {
return "name=" + name + ", number=" + number + ", birthday=" + birthday.toDateString() + "]";//不写toDateString(),则birthday会默认调用toString()是地址值
}
}
public class MyDate {
private int year;
private int month;
private int day;
public MyDate(int year, int month, int day) {
super();
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
public String toDateString(){
return year + "年" + month + "月" + day + "日";
}
}
public class SalariedEmployee extends Employee{
private double monthlySalary; //月工资
public SalariedEmployee(String name,int number,MyDate birthday) {
super(name,number,birthday);
}
public SalariedEmployee(String name, int number, MyDate birthday, double monthlySalary) {
super(name, number, birthday);
this.monthlySalary = monthlySalary;
}
@Override
public double earnings() {//重写earnings()抽象方法
return monthlySalary;
}
@Override
public String toString() {
return "SalariedEmployee [" + super.toString() + "]";
}
}
public class HourlyEmployee extends Employee{
private int wage; //每小时的工资
private int hour; //月工作的小时数
public HourlyEmployee(String name, int number, MyDate birthday) {
super(name, number, birthday);
}
public HourlyEmployee(String name, int number, MyDate birthday, int wage, int hour) {
super(name, number, birthday);
this.wage = wage;
this.hour = hour;
}
@Override
public double earnings() {
return wage*hour;
}
public int getWage() {
return wage;
}
public void setWage(int wage) {
this.wage = wage;
}
public int getHour() {
return hour;
}
public void setHour(int hour) {
this.hour = hour;
}
public String toString(){
return "HourlyEmployee[" + super.toString() + "]";
}
}
import java.util.Calendar;
import java.util.Scanner;
public class PayrollSystem {
public static void main(String[] args) {
//方式一:
// Scanner scanner = new Scanner(System.in);
// System.out.println("请输入当月的月份:");
// int month = scanner.nextInt();
//方式二:
Calendar calendar = Calendar.getInstance();
int month = calendar.get(Calendar.MONTH);//获取当前的月份
// System.out.println(month);//一月份:0
Employee[] emps = new Employee[2];
emps[0] = new SalariedEmployee("马良", 1002,new MyDate(1992, 2, 28),10000);
emps[1] = new HourlyEmployee("博西", 2001, new MyDate(1991, 1, 6),60,240);
for(int i = 0;i < emps.length;i++){
System.out.println(emps[i]);
double salary = emps[i].earnings();
System.out.println("月工资为:" + salary);
if((month+1) == emps[i].getBirthday().getMonth()){
System.out.println("生日快乐!奖励 100 元");
}
}
}
}
5.5、多态的应用:模板方法设计模式
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题
- 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
举例 1
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() {//1000以内的质数
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);
}
}
}
}
举例 2
//抽象类的应用:模板方法的设计模式
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、HibernateTemplate 等
- 一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java 不支持多重继承。有了接口,就可以得到多重继承的效果。
- 另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有 is-a 的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3 机、手机、数码相机、移动硬盘等都支持 USB 连接。
- 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是"能不能"的关系。
- 接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
-
1.接口使用 interface 来定义。
-
2.在 Java 中:接口和类是并列的两个结构
-
3.如何去定义两个接口:定义接口中的成员
-
3.1 JDK7 及以前:只能定义全局静态常量和抽象方法
全局常量:public static final 的,书写中可以省略不写。
抽象方法:public abstract 的,书写中可以省略不写。
-
3.2 JDK8:除了全局常量和抽象方法之外,还可以定义静态方法、默认方法(见–6.7、Java 8 中关于接口的改进)
-
-
4.接口中不能定义构造器!意味着接口不可以实例化。
-
5.Java 开发中,接口通过让类去实现(implements)的方式来使用。
如果实现类覆盖了接口中的所有方法,则此实现类就可以实例化
如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
-
6.Java 类可以实现多个接口 —》弥补了 Java 单继承性的局限性
格式:class AA extends BB implementd 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();
void stop();//省略了public abstract
//接口中不能定义构造器
// 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() {
// TODO Auto-generated method stub
}
@Override
public void fly() {
// TODO Auto-generated method stub
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
@Override
public void method1() {
// TODO Auto-generated method stub
}
@Override
public void method2() {
// TODO Auto-generated method stub
}
}
/
public class Circle {
private double radius;
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public Circle() {
super();
}
public Circle(double radius) {
super();
this.radius = radius;
}
}
public class ComparableCircle extends Circle implements CompareObject{
public ComparableCircle(double radius) {//因为需要用ComparableCircle的对象调用compareTo(),所以用ComparableCircle创建圆
super(radius);
}
@Override
public int compareTo(Object o) {
if(this == o){//同一个引用
return 0;
}
if(o instanceof ComparableCircle){//先强转,是个圆
ComparableCircle c = (ComparableCircle)o;
//错误的:有漏洞,丢失精度
// return (int) (this.getRadius() - c.getRadius());
//正确的方式一:
if(this.getRadius() > c.getRadius()){
return 1;
}else if(this.getRadius() < c.getRadius()){
return -1;
}else{
return 0;
}
//正确的方式二:当属性radius声明为Double类型时,可以调用包装类的方法
// return this.getRadius().compareTo(c.getRadius());//compareTo()是Double类中的
}else{
return 0;
//或者
// throw new RuntimeException("传入的数据类型不匹配");
}
}
}
public class ComparableCircleTest {
public static void main(String[] args) {
ComparableCircle c1 = new ComparableCircle(3.4);
ComparableCircle c2 = new ComparableCircle(3.6);
int compareValue = c1.compareTo(c2);//比较大小
if(compareValue > 0){
System.out.println("c1对象大");
}else if(compareValue < 0){
System.out.println("c2对象大");
}else{
System.out.println("c1与c2一样大");
}
//正确的方式二的检测
// int compareValue1 = c1.compareTo(new String("AA"));
// System.out.println(compareValue1);
}
}
6.7、Java 8 中关于接口的改进
Java 8 中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
- 静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像 Collection/Collections 或者 Path/Paths 这样成对的接口和类。
- 默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java 8 API 中对 Collection、List、Comparator 等接口提供了丰富的默认方法。
public interface CompareA {
//静态方法
public static void method1() {
System.out.println("CompareA:西安");
}
//默认方法
public default void method2(){
System.out.println("CompareA:深圳");
}
//public可以省略
default void method3(){
System.out.println("CompareA:杭州");
}
}
public interface CompareB {
default void method3(){
System.out.println("CompareB:上海");
}
}
public class SuperClass {
public void method3(){
System.out.println("SuperClass:北京");
}
}
public class SubClassTest {
public static void main(String[] args) {
SubClass s = new SubClass();
// s.method1();//错误
// SubClass.method1();//错误
// 知识点 1:接口中定义的静态方法,只能通过接口来调用,不能通过实现类或实现类的对象调用
CompareA.method1();
// 知识点 2:通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
s.method2();//SubClass:上海
// 知识点 3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
// 那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则
// 知识点 4:如果实现类实现了多个接口(此时没有继承父类),而这多个接口中定义了同名同参数的默认方法,
// 那么在实现类没有重写此方法的情况下,报错。-->接口冲突。
// 这就需要我们必须在实现类中重写此方法
s.method3();
}
}
class SubClass extends SuperClass implements CompareA,CompareB{
public void method2(){
System.out.println("SubClass:上海");
}
public void method3(){//在类中重写接口的默认方法,default要去掉
System.out.println("SubClass:深圳");
}
// 知识点 5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod(){
method3(); //调用自己定义的重写的方法
super.method3(); //调用的是父类中声明的
// 调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}
}
6.8、默认方法接口冲突问题
-
若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:接口冲突
-
解决1:调用其中一个接口中的方法
接口名.super.方法名();
-
解决2:实现类覆盖接口中同名同参数的方法,用实现类的对象调用
-
-
若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则,接口中具有相同名称和参数的默认方法会被忽略
-
解决1:默认调用父类的方法,类优先原则
-
解决2:重写该方法,用对象调用方法
-
解决3:调用接口中的方法
接口名.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 MySub extends Inner{//继承内部类需要导包
MySub(Outer out){
out.super();//需要外部类的对象,才能调用非静态内部类的构造器
}
@Override
public void test() {
System.out.println("hello");
}
}
7.3、局部内部类
- 1.声明在方法体内
- 2.局部内部类的修饰符,只能有abstract或final
- 3.有作用域
- 4.如果局部内部类在静态方法中,不能使用外部类的非静态成员
- 5.在局部内部中,可以使用当前局部内部类所在方法的局部变量,但是要求,这个局部变量必须是final的常量,原因:为了避免局部内部类对象被返回到外部类的外面使用时,访问不到这个局部变量,所以要把这个局部变量变为final的常量
- jdk 7及之前版本:要求此局部变量显式的声明为final的
- jdk 8及之后的版本:会自动添加final变为常量,可以省略final的声明
public class TestLocalInner {
public static void main(String[] args) {
Outer out = new Outer();
Father in = out.test();//在外部类的外面虽然不能使用局部内部类,但是可以得到它的对象
System.out.println(in.getClass());
in.method();//输出10,在这里仍然可以访问到这个a,那么这个a就不存在栈中而存在方法区中,是局部常量
}
}
abstract class Father{
public abstract void method();
}
class Outer{
private int i = 1;//成员变量,实例变量,非静态成员变量
private static int j = 2;//成员变量,类变量,静态变量
public Father test(){
// Inner in = new Inner();//错误,有作用域,尽管声明在在该方法内,但是局部内部类未开始加载
final int a = 10;//局部变量==>局部常量
//局部内部类
class Inner extends Father{
public void method(){
System.out.println(i);
System.out.println(j);
System.out.println(a);
}
}
Inner in = new Inner();//创建对象,必须在该方法内,并且局部内部类要开始加载才行
in.method();
return in;
}
public void method(){
// Inner in = new Inner();//错误,有作用域
}
public static void fun(){
//局部内部类
class Inner{
public void method(){
// System.out.println(i);//是因为fun方法是静态的,第4条
System.out.println(j);
}
}
}
}
7.4、匿名内部类
7.4.1、匿名内部类描述
-
1.匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例;一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
-
语法格式:
new 父类名(实参列表){ //匿名内部类的类体部分 } 说明:如果你子类调用的是父类的无参构造,那么()中实参列表不用写,如果子类调用的是父类的有参构造,那么就在()中传入实参列表new 父接口名(){ //匿名内部类的类体部分 }**特殊:**声明匿名内部类与创建它的对象是一起完成的。即匿名内部类只有唯一的对象
-
3.匿名内部类的特点
- 匿名内部类必须继承父类或实现接口
- 声明匿名内部类与创建它的对象是一起完成的,即匿名内部类只有唯一的对象
- 匿名内部类对象只能使用多态形式引用
import java.util.Arrays;
import java.util.Comparator;
public class TestAnonymousInner {
public static void main(String[] args) {
//1、需求1:要声明一个Object的子类,匿名子类,并在子类中声明一个方法public void test(){}打印“hello匿名内部类"
//下面这段代码,声明了匿名内部类,也创建了它的对象,但是没有说这个对象干什么,编译不通过的。
// new Object(){
// public void test(){
// System.out.println("hello匿名内部类");
// }
// }
//(1)我们可以把这个对象,赋值给一个变量
//多态引用(注意)
Object obj1 = new Object(){
public void test(){
System.out.println("hello匿名内部类");
}
};
System.out.println(obj1.getClass());//获取对象的运行时类型
System.out.println(obj1);//打印对象时,自动调用对象的toString(),调用Object类的toString()
//这是另一个Object的匿名子类的对象
// Object obj2 = new Object(){
// public void test(){
// System.out.println("hello匿名内部类");
// }
// };
// (2)我们可以用这个对象,直接调用方法
//匿名内部类的匿名对象.方法()
new Object(){
public void test(){
System.out.println("hello匿名内部类");
}
}.test();
/
Circle[] all = new Circle[3];
all[0] = new Circle(3);
all[1] = new Circle(2);
all[2] = new Circle(1);
//匿名内部类的匿名对象作为实参使用
//Arrays.sort(all, c){ }
Arrays.sort(all, new Comparator(){
@Override
public int compare(Object o1, Object o2) {
Circle c1 = (Circle) o1;
Circle c2 = (Circle) o2;
if(c1.getRadius() > c2.getRadius()){
return 1;
}else if(c1.getRadius() < c2.getRadius()){
return -1;
}
return 0;
}
});
}
}
class Circle{
private double radius;
public Circle(double radius) {
super();
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public String toString() {
return "Circle [radius=" + radius + "]";
}
}
7.4.2、练习
练习1
【题目描述】
(1)声明一个抽象类Father,包含抽象方法:public abstract void method(); (2)用匿名内部类继承Father,并重写抽象方法,打印“hello 孩子",并调用子类对象的method方法
【代码实现】
public class TestExer3 {
public static void main(String[] args) {
//方式一
//方式二
Father f = new Father(){
public void method(){
System.out.println("hello 孩子");
}
};
f.method();
}
}
abstract class Father{
public abstract void method();
}
练习2
【题目描述】
(1)声明一个员工类Employee,有属性:编号、姓名、薪资 (2)在测试类中创建Employee数组 (3)分别调用Arrays.sort(数组,Comparator),用匿名内部类实现按照编号升序排列 (4)分别调用Arrays.sort(数组,Comparator),用匿名内部类实现按照薪资升序排列
【代码实现】
import java.util.Arrays;
import java.util.Comparator;
public class TestExer4 {
@SuppressWarnings("all")//抑制所有代码的警告
public static void main(String[] args) {
//(2)在测试类中创建Employee数组
Employee[] all = new Employee[3];
all[0] = new Employee(2, "张三", 10000);
all[1] = new Employee(1, "李四", 30000);
all[2] = new Employee(3, "王五", 20000);
//(3)分别调用Arrays.sort(数组,Comparator),用匿名内部类实现按照编号升序排列
Arrays.sort(all, new Comparator(){
@Override
public int compare(Object o1, Object o2) {
Employee e1 = (Employee) o1;
Employee e2 = (Employee) o2;
return e1.getId() - e2.getId();
}
});
for (int i = 0; i < all.length; i++) {
System.out.println(all[i]);
}
// (4)分别调用Arrays.sort(数组,Comparator),用匿名内部类实现按照薪资升序排列
Arrays.sort(all , new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Employee e1 = (Employee) o1;
Employee e2 = (Employee) o2;
if(e1.getSalary() > e2.getSalary()){
return 1;
}else if(e1.getSalary() < e2.getSalary()){
return -1;
}
return 0;
}
});
for (int i = 0; i < all.length; i++) {
System.out.println(all[i]);
}
}
}
//(1)声明一个员工类Employee,有属性:编号、姓名、薪资
class Employee{
private int id;
private String name;
private double salary;
public Employee(int id, String name, double salary) {
super();
this.id = id;
this.name = name;
this.salary = salary;
}
public Employee() {
super();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", salary=" + salary + "]";
}
}
7.5、练习
【题目描述】
【代码实现】
//器官类型
public abstract class Organ {
public abstract void doWork();
}
import java.util.Random;
public class Body {
private String owner;
private double weight;//克
private boolean health;
private Organ heart;
public Body(String owner, double weight, boolean health) {
super();
this.owner = owner;
this.weight = weight;
this.health = health;
Heart heart = new Heart();
heart.size = weight * 0.005;
Random rand = new Random();
if(health){
heart.color = "鲜红色";
//rand.nextInt(41)==>[0,41),[0,40]
//60-100
heart.beatPerMinute = rand.nextInt(41) + 60;
}else{
heart.color = "暗红色";
//[0,60) 或 [101,~)
heart.beatPerMinute = rand.nextInt(60);
}
this.heart = heart;
}
private class Heart extends Organ{
int beatPerMinute;//每分钟跳动次数
String color;
double size;
@Override
public void doWork() {
System.out.println("心率:" + beatPerMinute + ",大小:" + size + ",颜色:" + color);
//这里的owner属性是创建Heart对象的那个Body对象
//即非静态内部类的对象,一直记录着是哪个外部对象创建的它 Body.this
// System.out.println("为血液流动提供动力,把血液运行至" + owner + "身体各个部分");
System.out.println("为血液流动提供动力,把血液运行至" + Body.this.owner + "身体各个部分");
}
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public boolean isHealth() {
return health;
}
public void setHealth(boolean health) {
this.health = health;
}
public Organ getHeart() {
return heart;
}
public void setHeart(Organ heart) {
this.heart = heart;
}
@Override
public String toString() {
return "姓名:" + owner + ", 体重:" + weight + ",健康与否:" + health;
}
}
public class Doctor {
public void swapHeart(Body b1, Body b2){
Organ temp = b1.getHeart();
b1.setHeart(b2.getHeart());
b2.setHeart(temp);
boolean hTemp = b1.isHealth();
b1.setHealth(b2.isHealth());
b2.setHealth(hTemp);
}
}
public class TestExer {
public static void main(String[] args) {
Body b1 = new Body("张三", 50000, true);
Body b2 = new Body("李四",100000,false);
System.out.println(b1);
b1.getHeart().doWork();
System.out.println(b2);
b2.getHeart().doWork();
System.out.println("============================");
Doctor d = new Doctor();
d.swapHeart(b1, b2);
System.out.println(b1);
b1.getHeart().doWork();
System.out.println(b2);
b2.getHeart().doWork();
}
}
【运行结果】



