第6章、面向对象编程(下)
一、关键字:static
/**
* static关键字的使用
* 1、static: 静态的
* 2、static可以用来修饰: 属性、方法、代码块、内部类
* 3、使用static修饰属性:静态变量(类变量)
* (1)属性: 按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
*
* 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性,
* 当修改一个对象中的非静态属性,不会导致其它对象中同样的属性值的修改。
*
* 静态变量:我们创建了类的多个对象,每个对象共享一个静态变量,当通过某一个对象修改静态变量时,
* 会导致其它对象调用此静态变量时,是修改过了的。
*
* (2)static修饰属性的其它说明
* 静态变量随着类的加载而加载。可以通过 “类.静态变量” 的方式进行调用
* 静态变量的加载要早于对象的创建
* 由于类只会加载一次,则静态变量在内存中也只存在一份,存在方法区的静态域中。
*
* 类变量 实例变量
* 类 yes no
* 对象 yes yes
*
* (3)静态属性举例:System.out、Math.PI
*
* 4、使用static修饰方法: 静态方法
* (1)随着类的加载而加载,可以通过 “类.静态方法” 的方式进行调用
*
* 静态方法 非静态方法
* 类 yes no
* 对象 yes yes
*
* 5、static注意点
* 在静态的方法内,不能使用this关键字、super关键字
* 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
*
* 6、开发中,如何确定一个属性是否要声明为static的?
* 属性是可以被多个对象所共享的,不会随着对象的不同而不同。
* 类中的常量也常常声明为static
*
* 开发中,如何确定一个方法是否要声明为static的?
* 操作静态属性的方法,通常设置为static的
* 工具类中的方法,习惯上声明为static的。比如:Math、Arrays、Collections
*
*
*
* @author: huxingxin
* @date: 2022/12/13 10:19:43
*/
public class StaticTest {
public static void main(String[] args) {
Chinese.setNation("中国"); //调用静态方法
Chinese.show(); //调用静态方法
Chinese chinese1 = new Chinese();
chinese1.setName("张三");
chinese1.setAge(27);
chinese1.eat();
chinese1.show(); //调用静态方法
// chinese1.setNation("中国"); //调用静态方法
System.out.println(chinese1);
Chinese chinese2 = new Chinese();
chinese2.setName("李四");
chinese2.setAge(30);
System.out.println(chinese2);
}
}
class Chinese{
private String name;
private int age;
private static String nation;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static String getNation() {
return nation; //调用类变量
}
public static void setNation(String nation) {
Chinese.nation = nation; //调用类变量
}
public void eat(){
System.out.println("中国人吃中餐");
}
public static void show(){
System.out.println("我是中国人");
}
@Override
public String toString() {
return "Chinese{" +
"name='" + name + '\'' +
", age=" + age +
", nation='" + nation + '\'' +
'}';
}
}
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分
配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间
里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表
国家名称的变量
class Circle{
private double radius;
public Circle(double radius){
this.radius=radius;
}
public double findArea(){
return Math.PI*radius*radius;
}
}
创建两个Circle对象
Circle c1=new Circle(2.0); //c1.radius=2.0
Circle c2=new Circle(3.0); //c2.radius=3.0
Circle类中的变量radius是一个实例变量(instance variable),它属于类的每一个对象,不能被同一个类的不同对象所共享。
上例中c1的radius独立于c2的radius,存储在不同的空间。c1中的radius变化不会影响c2的radius,反之亦然。
如果想让一个类的所有实例共享数据,就用类变量!
1、类属性、类方法的设计思想
类属性作为该类各个对象之间共享的变量。在设计类时,分析哪些属性不因对象的不同而改变,将这些属性设置为类属性。相应的方法设置为类方法
如果方法与调用者无关,则这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。
使用范围:在Java类中,可用static修饰属性、方法、代码块、内部类
被修饰后的成员具备以下特点
- 随着类的加载而加载
- 优先于对象存在
- 修饰的成员,被所有对象所共享
- 访问权限允许时,可不创建对象,直接被类调用
public class StaticTest {
public static void main(String[] args) {
Circle c1 = new Circle(2.0);
Circle c2 = new Circle(3.0);
c1.display();
c2.display();
}
}
class Circle {
private double radius;
public static String name = "这是一个圆";
public static String getName() {
return name;
}
public Circle(double radius) {
this.radius = radius;
}
public double findArea() {
return Math.PI * radius * radius;
}
public void display() {
System.out.println("name:" + name + "radius:" + radius);
}
}
2、类变量(class Variable)
类变量(类属性)由该类的所有实例共享
public class Person {
private int id;
public static int total = 0;
public Person() {
total++;
id = total;
}
}
(1)类变量应用举例
public class StaticDemo {
public static void main(String args[]) {
Person.total = 100; // 不用创建对象就可以访问静态成员
//访问方式:类名.类属性,类名.类方法
System.out.println(Person.total);
Person c = new Person();
System.out.println(c.total); //输出101
}
}
class Person {
private int id;
public static int total = 0;
public Person() {
total++;
id = total;
}
public static void main(String args[]){
Person Tom=new Person();
Tom.id=0;
total=100; // 不用创建对象就可以访问静态成员
}
}
(2)类变量 vs 实例变量内存解析
(3)静态变量的内存解析
3、类方法(class method)
没有对象的实例时,可以用类名.方法名()
的形式访问由static修饰的类方法。
在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。
public class PersonTest {
public static void main(String[] args) {
System.out.println("Number of total is " + Person.getTotalPerson());
//没有创建对象也可以访问静态方法
Person p1 = new Person();
System.out.println( "Number of total is "+ Person.getTotalPerson());
}
}
class Person {
private int id;
private static int total = 0;
public static int getTotalPerson() {
//id++; //非法
return total;
}
public Person() {
total++;
id = total;
}
}
因为不需要实例就可以访问static方法,因此static方法内部不能有this。(也不能有super ? YES!)
static修饰的方法不能被重写
public class PersonTest {
public static void main(String[] args) {
Person.setTotalPerson(3);
}
}
class Person {
private int id;
private static int total = 0;
public static void setTotalPerson(int total){
this.total=total; //非法,在static方法中不能有this,也不能有super
}
public Person() {
total++;
id = total;
}
}
4、应用
/**
* @author: huxingxin
* @date: 2022/12/13 21:09:30
*/
public class Static关键字的应用 {
public static void main(String[] args) {
Circle circle1 = new Circle(1);
System.out.println(circle1);
double area1 = circle1.findArea();
System.out.println("circle1的面积:" + area1);
System.out.println();
Circle circle2 = new Circle(2);
System.out.println(circle2);
double area2 = circle2.findArea();
System.out.println("circle2的面积:" + area2);
}
}
class Circle{
private double radius;//半径
private int id;//序号
private static int total;//记录Circle实例被创建的个数
//初始化序号
private static int init = 1001;//static声明的属性被所有对象所共享
public Circle() {
id = init++;
total++;
}
public Circle(double radius) {
this();
this.radius = radius;
// this.id = init++;
// total++;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public int getId() {
return id;
}
public static int getTotal() {
return total;
}
public double findArea(){
return Math.PI * Math.pow(radius, 2);
}
@Override
public String toString() {
return "Circle{" +
"radius=" + radius +
", id=" + id +
", total=" + total +
'}';
}
}
4、练习
(1)编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。账号要自动生成。编写主类,使用银行账户类,输入、输出3个储户的上述信息。考虑:哪些属性可以设计成static属性。
/**
* @author: huxingxin
* @date: 2022/12/14 9:08:59
*/
public class AccountTest {
public static void main(String[] args) {
Account account1 = new Account();
System.out.println(account1);
account1.withdrawal(1000);
account1.deposit(2000);
account1.withdrawal(1000);
System.out.println();
Account.setInterestRate(0.014);
Account account2 = new Account("123456", 2000);
System.out.println(account2);
account2.withdrawal(1000);
account2.deposit(2000);
account2.withdrawal(1000);
}
}
import com.notes.Bank;
/**
* 编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,定义封装这些属性的方法。
* 账号要自动生成。编写主类,使用银行账户类,输入、输出3个储户的上述信息。考虑:哪些属性可以设计成static属性。
*
* @author: huxingxin
* @date: 2022/12/14 8:56:25
*/
public class Account{
private int id;//帐号
private String password;//密码
private double balance;//余额
private static int initAccount = 10001;//初始帐号
private static double interestRate = 0.015;//利率
private static double minBalance = 1000;//最小余额
public Account() {
id = initAccount++;
password = "000000";
}
public Account(String password, double balance) {
this();
this.password = password;
this.balance = balance;
}
public int getId() {
return id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public double getBalance() {
return balance;
}
public static double getInterestRate() {
return interestRate;
}
public static void setInterestRate(double interestRate) {
Account.interestRate = interestRate;
}
/**
*存钱
* @param amount 存钱金额
*/
public void deposit(double amount){
if (amount > 0){
balance += amount;
System.out.println("存款成功, 取款金额: " + amount + ", 余额: " + balance);
}else {
System.out.println("存款失败, 存款金额: " + amount + ", 余额:" + balance);
}
}
/**
* 取钱
* @param amount 取钱金额
*/
public void withdrawal(double amount){
if (balance >= amount){
balance -= amount;
System.out.println("取款成功, 取款金额: " + amount + ", 余额: " + balance);
}else {
System.out.println("取款失败,余额不足, 取款金额: " + amount + ", 余额: " + balance);
}
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", password='" + password + '\'' +
", balance=" + balance +
", interestRate=" + interestRate +
", minBalance=" + minBalance +
'}';
}
}
(2)自定义ArrayUtil工具类的优化
/**
* @author: huxingxin
* @date: 2022/12/13 20:52:30
*/
public class ArrayUtilTest {
public static void main(String[] args) {
int[] arr = {100, 23, 54, 65, 32, 88};
int max = ArrayUtil.getMax(arr);
System.out.println(max);
ArrayUtil.sort(arr);
ArrayUtil.print(arr);
}
}
/**
* 自定义ArrayUtil工具类的优化
*/
class ArrayUtil{
//求数组的最大值
public static int getMax(int[] arr){
int max = arr[0];
for (int i = 1; i < arr.length; i++){
if (max < arr[i]){
max = arr[i];
}
}
return max;
}
//求数组的最小值
public static int getMin(int[] arr){
int min = arr[0];
for (int i = 1; i < arr.length; i++){
if (min > arr[i]){
min = arr[i];
}
}
return min;
}
//求数组的总和
public static int getCount(int[] arr){
int count = 0;
for (int i = 0; i < arr.length; i++){
count += arr[i];
}
return count;
}
//求数组的平均值
public static double getAvg(int[] arr){
int count = getCount(arr);
return count / (double)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 - 1 - i];
arr[arr.length - 1 - i] = temp;
}
}
//复制数组
public static int[] copy(int[] arr){
int[] temp = new int[arr.length];
for (int i = 0; i < arr.length; i++){
temp[i] = arr[i];
}
return temp;
}
//数组排序
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++){
int temp = arr[j];
if (temp > arr[j + 1]){
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
//遍历数组
public static void print(int[] arr){
String s = "[";
for (int i = 0; i < arr.length; i++){
if (i < arr.length - 1){
s += arr[i] + ", ";
}else {
s += arr[i];
}
}
s += "]";
System.out.println(s);
}
//查找指定元素
public static int getIndex(int[] arr, int value){
for (int i = 0; i < arr.length; i++){
if (value == arr[i]){
return i;
}
}
return -1;
}
}
5、单例 (Singleton)设计模式
/**
* 例设计模式:
* 1、就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。
* 2、如何实现 ?
* 饿汉式 vs 懒汉式
*
* 3、区分饿汉式和懒汉式
* 饿汉式:坏处: 对象加载时间过长
* 好处:饿汉式是线程安全的
* 懒汉式:好处:延迟对象的创建
* 目前写法的坏处:线程不安全
*
* @author: huxingxin
* @date: 2022/12/14 10:00:53
*/
public class SingletonTest1 {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println("bank1 == bank2: " + (bank1 == bank2));
}
}
/**
* 饿汉式
*/
class Bank{
//1、私有化类的构造器
private Bank(){
}
//2、内部创建类的对象
//4、要求此对象也要声明为静态的
public static Bank bank = new Bank();
//3、提供公共的静态的方法,返回类的对象
public static Bank getInstance(){
return bank;
}
}
/**
* 单例模式懒汉式的实现
*
* @author: huxingxin
* @date: 2022/12/14 10:09:30
*/
public class SingletonTest2 {
public static void main(String[] args) {
People people1 = People.getInstance();
People people2 = People.getInstance();
System.out.println("people1 == people2: " + (people1 == people2));
}
}
class People{
//1、私有化构造器
private People() {
}
//2、声明当前类对象,没有初始化
//4、要求此对象也要声明为静态的
private static People people = null;
//3、提供公共的静态的方法,返回类的对象
public static People getInstance(){
if (people == null){
people = new People();
}
return people;
}
}
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。“套路”
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
(1)饿汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single = new Singleton();
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
return single;
}
}
(2)懒汉式
class Singleton {
// 1.私有化构造器
private Singleton() {
}
// 2.内部提供一个当前类的实例
// 4.此实例也必须静态化
private static Singleton single;
// 3.提供公共的静态的方法,返回当前类的对象
public static Singleton getInstance() {
if(single == null) {
single = new Singleton();
}
return single;
}
}
懒汉式暂时还存在线程安全问题,讲到多线程时,可修复
(3)单例模式的优点
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
(4)举例 java.lang.Runtime
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
(5)应用场景
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
- Application 也是单例的典型应用
- Windows的Task Manager (任务管理器)就是很典型的单例模式
- Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
二、理解main方法的语法
/**
* main() 方法的使用说明
* 1、main()方法作为程序的入口
* 2、main()方法也是一个普通的静态方法
* 3、main()方法可以作为我们与控制台交流的一种方式。(之前:使用Scanner)
*
* @author: huxingxin
* @date: 2022/12/14 11:31:22
*/
public class MainTest {
public static void main(String[] args) {
//main()方法可以作为我们与控制台交流的一种方式。(之前:使用Scanner)
for (int i = 0; i < args.length; i++){
System.out.println(args[i]);
}
//main()方法也是一个普通的静态方法
// Main.main(new String[]{"hello", "word", "java"});
}
}
class Main{
public static void main(String[] args) {
for (int i = 0; i < args.length; i++){
System.out.println(args[i]);
}
}
}
由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到
public class CommandPara {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "] = " + args[i]);
}
}
}
#运行程序CommandPara.java
java CommandPara “Tom" “Jerry" “Shkstart"
输出结果:
args[0] = Tom
args[1] = Jerry
args[2] = Shkstart
1、面试题
//此处,Something类的文件名叫OtherThing.java
class Something {
public static void main(String[] something_to_do) {
System.out.println("Do something ...");
}
}
//上述程序是否可以正常编译、运行?
三、类的成员之四:代码块
/**
* 类的成员之四:代码块(或初始化块)
* 1、代码块的作用:用来初始化类、对象
* 2、代码块如果有修饰的话,只能用static
* 3、分类:静态代码块 vs 非静态代码块
*
* 4、静态代码块
* > 内部可以有输出语句
* > 随着类的加载而执行,而且只执行一次
* > 作用:初始化类的信息
* > 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
* > 静态代码块的执行要优先于非静态代码块的执行
* > 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
*
* 5、非静态代码块
* > 内部可以有输出语句
* > 随着对象的创建而执行
* > 每创建一个对象,就执行一次非静态代码块
* > 作用:可以在创建对象时,对对象的属性等进行初始化
* > 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
* > 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
*
* @author: huxingxin
* @date: 2022/12/14 14:32:34
*/
public class BlockTest {
public static void main(String[] args) {
Person.getDesc();
Person person1 = new Person();
System.out.println(person1);
System.out.println("age: " + person1.getAge());
Person person2 = new Person();
System.out.println(person2);
}
}
class Person{
/**
* 属性
*/
private String name;
private int age;
private static String desc = "我是一个人";
/**
* 构造器
*/
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 代码块
*/
//static的代码块
static{
System.out.println("static的代码块-1");
}
static{
System.out.println("static的代码块-2");
desc = "我是一个爱学习的人";
}
//非static的代码块
{
System.out.println("非static的代码块-1");
age = 28;
}
{
System.out.println("非static的代码块-2");
}
/**
* 方法
* @return
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static String getDesc() {
return desc;
}
public static void setDesc(String desc) {
Person.desc = desc;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", desc='" + desc + '\'' +
'}';
}
}
- 代码块(或初始化块)的作用:对Java类或对象进行初始化
- 代码块(或初始化块)的分类:
- 一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块(static block)
- 没有使用static修饰的,为非静态代码块
- static代码块通常用于初始化static的属性
class Person {
public static int total;
static {
total = 100;//为total赋初值
}
…… //其它属性或方法声明
}
1、静态代码块
- 用static 修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态代码块的执行要先于非静态代码块。
- 静态代码块随着类的加载而加载,且只执行一次。
2、非静态代码块
- 没有static修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 除了调用非静态的结构外,还可以调用静态的变量或方法。
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
- 每次创建对象的时候,都会执行一次。且先于构造器执行。
3、静态初始化块举例
public class PersonTest {
public static void main(String[] args) {
System.out.println("total = " + Person.total);
System.out.println("total = " + Person.total);
}
}
class Person {
public static int total;
static {
total = 100;
System.out.println("in static block!");
}
}
5、总结:程序中成员变量赋值的执行顺序
/**
* 对属性可以赋值的位置
* (1)默认初始化
* (2)显式初始化 / (5)在代码块中赋值 : 执行顺序,看结构的先后顺序
* (3)构造器中初始化
* (4)有了对象以后,可以通过 "对象.属性" 或 "对象.方法"的方式,进行赋值
*
*
* 执行的先后顺序 (1)-(2)/(5)-(3)-(4)
*
* @author: huxingxin
* @date: 2022/12/14 17:06:57
*/
public class Demo {
public static void main(String[] args) {
Order order = new Order();
System.out.println(order.age); //28
}
}
class Order{
{
age = 29;
}
int age = 28;
// {
// age = 29;
// }
}
6、练习
以下代码的输出结果是什么
class Root{
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root(){
System.out.println("Root的无参数的构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid(){
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();
//new 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();
}
}
四、关键字:final
/**
* final: 最终的
* 1、final可以用来修饰的结构:类、方法、变量
*
* 2、final 用来修饰一个类:此类不能被其它类所继承,
* 比如 String类、System类、StringBuffer类
*
* 3、final 用来修饰方法:表明此方法不可以被重写
* 比如:Object类中 getClass()
*
* 4、final 用来修饰变量:此时的 “变量” 就称为是一个常量
* final 修饰属性:可以考虑赋值的位置有 显式初始化、代码块中初始化、构造器中初始化
* final 修饰局部变量:
* 尤其是使用final修饰形参时,表明此参数是一个常量,当我们调用此方法时,给常量形参赋一个实参,一旦复制以后,就只能在方法体内使用此形参,但不能进行重新赋值
*
* static final 用来修饰属性:全局常量
* 方法
*
* @author: huxingxin
* @date: 2022/12/14 17:18:08
*/
public class FinalTest {
// final int a; //Variable 'a' might not have been initialized
// final int a = 10; //显式初始化
final int a;
// { //代码块中初始化
// a = 10;
// }
public _FinalTest() {
a = 10;
}
public void doWith(){
// a = 10; //Variable 'a' might not have been initialized
final int a;
a = 100;
}
public void show(final int a){
// a = 100; //Cannot assign a value to final variable 'a'
System.out.println(a);
}
public static void main(String[] args) {
int num = 10;
num += 5;
_FinalTest finalTest = new _FinalTest();
finalTest.show(100);
}
}
final class People{
}
/*class Man extends People{ //Cannot inherit from final 'com.notes._1Java基础编程._6面向对象编程_下._6final修饰类和方法.People'
}*/
class Animal{
public final void eat(){
}
}
class Cat extends Animal{
/* @Override
public void eat() { //eat()' cannot override 'eat()' in 'com.notes._1Java基础编程._6面向对象编程_下._6final修饰类和方法.Animal'; overridden method is final
}*/
}
在Java中声明类、变量和方法时,可使用关键字final
来修饰,表示“最终的”。
- final标记的类不能被继承。提高安全性,提高程序的可读性。String类、System类、StringBuffer类
- final标记的方法不能被子类重写。比如:Object类中的getClass()。
- final标记的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。
- final标记的成员变量必须在声明时或在每个构造器中或代码块中显式赋值,然后才能使用。
final double MY_PI = 3.14
;
1、 final修饰类
final class A{
}
class B extends A{ //错误,不能被继承。
}
中国古代,什么人不能有后代,就可以被final声明,称为“太监类”!
2、 final修饰方法
class A {
public final void print() {
System.out.println("A");
}
}
class B extends A {
public void print() { // 错误,不能被重写。
System.out.println("尚硅谷");
}
}
3、final修饰变量-常量
class A {
private final String INFO = "atguigu"; //声明常量
public void print() {
//The final field A.INFO cannot be assigned
//INFO = "尚硅谷";
}
}
常量名要大写,内容不可修改。——如同古代皇帝的圣旨
static final:全局常量
4、关键字final应用举例
public final class Test {
public static int totalNumber = 5;
public final int ID;
public Test() {
ID = ++totalNumber; // 可在构造器中给final修饰的“变量”赋值
}
public static void main(String[] args) {
Test t = new Test();
System.out.println(t.ID);
final int I = 10;
final int J;
J = 20;
J = 30; // 非法
}
}
5、面试题
(1)题目1
public class Something {
public int addOne(final int x) {
return ++x;
// return x + 1;
}
}
(2)题目2
public class Something {
public static void main(String[] args) {
Other o = new Other();
new Something().addOne(o);
}
public void addOne(final Other o) {
// o = new Other();
o.i++;
}
}
class Other {
public int i;
}
五、抽象类与抽象方法
/**
* abstract关键字的使用
* 1、abstract:抽象的
* 2、abstract可以用来修饰的结构: 类、方法
*
* 3、abstract修饰类:抽象类
* > 此类不能实例化
* > 抽象类中一定有构造器,便于子类实例化的时候调用(涉及子类对象实例化的全过程)
* > 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
*
* 4、abstract修饰方法:抽象方法
* > 抽象方法只有方法的声明,没有方法体
* > 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法。
* > 若子类重写了父类中的所有的抽象方法后,此子类方可实例化
* 若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
*
* @author: huxingxin
* @date: 2022/12/15 11:18:27
*/
public class AbstractTest {
public static void main(String[] args) {
//一旦Person类抽象了,就不可实例化
// Person person = new Person(); //'Person' is abstract; cannot be instantiated
Student student = new Student();
student.eat();
student.walk();
}
}
/**
* 抽象类
*/
abstract class Person{
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void eat(){
System.out.println("person吃东西");
}
/**
* 抽象方法
*/
public abstract void walk();
}
class Student extends Person{
public Student() {
}
public Student(String name, int age) {
super(name, age);
}
@Override
public void walk() {
System.out.println("student走路");
}
}
/**
* abstract使用中的注意点
* 1、abstract不能用来修饰:属性、构造器、代码块等结构
* 2、abstract不能用来修饰私有方法、静态方法、final的方法、final的类
* 3、
*
* @author: huxingxin
* @date: 2022/12/15 14:40:08
*/
public class Demo {
}
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个
父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
- 用abstract关键字来修饰一个类,这个类叫做抽象类。
- 用abstract来修饰一个方法,该方法叫做抽象方法。
- 抽象方法:只有方法的声明,没有方法的实现。以分号结束:比如:
public abstract void talk();
- 含有抽象方法的类必须被声明为抽象类。
- 抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍
为抽象类。 - 不能用abstract修饰变量、代码块、构造器;
- 不能用abstract修饰私有方法、静态方法、final的方法、final的类。
1、抽象类举例
abstract class A {
abstract void m1();
public void m2() {
System.out.println("A类中定义的m2方法");
}
}
class B extends A {
void m1() {
System.out.println("B类中定义的m1方法");
}
}
public class Test {
public static void main(String args[]) {
A a = new B();
a.m1();
a.m2();
}
}
2、抽象类应用
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
在航运公司系统中,Vehicle类需要定义两个方法分别计算运输工具的燃料效率和行驶距离。
问题:卡车(Truck)和驳船(RiverBarge)的燃料效率和行驶距离的计算方法完全不同。Vehicle类不能提供计算方法,但子类可以。
解决方案
Java允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。
Vehicle是一个抽象类,有两个抽象方法。
public abstract class Vehicle{
public abstract double calcFuelEfficiency(); //计算燃料效率的抽象方法
public abstract double calcTripDistance(); //计算行驶距离的抽象方法
}
public class Truck extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算卡车的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算卡车行驶距离的具体方法 }
}
public class RiverBarge extends Vehicle{
public double calcFuelEfficiency( ) { //写出计算驳船的燃料效率的具体方法 }
public double calcTripDistance( ) { //写出计算驳船行驶距离的具体方法}
}
注意:抽象类不能实例化 new Vihicle()是非法的
3、思考
问题1:为什么抽象类不可以使用final关键字声明?
问题2:一个抽象类中可以定义构造器吗?
问题3:是否可以这样理解:抽象类就是比普通类多定义了抽象方法,除了不能直接进行类的实例化操作之外,并没有任何的不同?
4、练习
编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。
提供必要的构造器和抽象方法:work()。
对于Manager类来说,他既是员工,还具有奖金(bonus)的属性。
请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问。
/**
* 编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。
* 提供必要的构造器和抽象方法:work()。
* 对于Manager类来说,他既是员工,还具有奖金(bonus)的属性。
* 请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问。
*
* @author: huxingxin
* @date: 2022/12/15 14:54:46
*/
public class ManagerTest {
public static void main(String[] args) {
Employee manager = new Manager("张三", 1001, 10000, 1000);
System.out.println(manager);
manager.work();
System.out.println();
Employee employee = new CommonEmployee("李四", 1002, 5000);
System.out.println(employee);
employee.work();
}
}
/**
*
* @author: huxingxin
* @date: 2022/12/15 14:51:52
*/
public abstract class Employee {
private String name;
private int id;
private double salary;
public Employee() {
}
public Employee(String name, int id, double salary) {
this.name = name;
this.id = id;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
/**
* 抽象方法
*/
public abstract void work();
}
/**
* @author: huxingxin
* @date: 2022/12/15 15:00:35
*/
public class CommonEmployee extends Employee{
public CommonEmployee() {
}
public CommonEmployee(String name, int id, double salary) {
super(name, id, salary);
}
@Override
public void work() {
System.out.println("CommonEmployee 工作中...");
}
@Override
public String toString() {
return "Manager{" +
"name='" + this.getName() + '\'' +
", id=" + this.getId() +
", salary=" + this.getSalary() +
'}';
}
}
/**
* @author: huxingxin
* @date: 2022/12/15 14:53:37
*/
public class Manager extends Employee {
private double bonus;
public Manager() {
}
public Manager(double bonus) {
this.bonus = bonus;
}
public Manager(String name, int id, double salary, double bonus) {
super(name, id, salary);
this.bonus = bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
@Override
public void work() {
System.out.println("Manager 工作中...");
}
@Override
public String toString() {
return "Manager{" +
"name='" + this.getName() + '\'' +
", id=" + this.getId() +
", salary=" + this.getSalary() +
", bonus=" + bonus +
'}';
}
}
5、抽象类的匿名子类
/**
* 抽象类的匿名子类
*
* @author: huxingxin
* @date: 2022/12/15 11:18:27
*/
public class AbstractTest {
public static void main(String[] args) {
//非匿名的类非匿名的对象
Student student = new Student();
method(student);
method(new Student()); //非匿名的类匿名的对象
//创建了一个匿名子类的对象
Person person = new Person(){
@Override
public void walk() {
System.out.println("Student 走路...");
}
};
method(person);
//创建匿名子类的匿名对象
method(new Person() {
@Override
public void walk() {
System.out.println("Student 走路...");
}
});
}
public static void method(Person person){
person.walk();
}
}
/**
* 抽象类
*/
abstract class Person{
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void eat(){
System.out.println("person吃东西");
}
/**
* 抽象方法
*/
public abstract void walk();
}
class Student extends Person {
public Student() {
}
public Student(String name, int age) {
super(name, age);
}
@Override
public void walk() {
System.out.println("student走路");
}
}
6、模板方法设计模式
//抽象类的应用:模板方法的设计模式
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万美元!!");
}
}
/**
* @author: huxingxin
* @date: 2022/12/15 15:46:07
*/
public class TemplateTest {
public static void main(String[] args) {
SpendTimeTemplate template = new SubTemplate();
template.spendTime();
}
}
abstract class SpendTimeTemplate{
/**
* 计算某段代码执行所需要花费的时间
*/
public void spendTime(){
long startTime = System.currentTimeMillis();
code();
long endTime = System.currentTimeMillis();
System.out.println("花费的时间: " + (endTime - startTime));
}
/**
* 不确定的部分、易变的部分
*/
public abstract void code();
}
class SubTemplate extends SpendTimeTemplate{
@Override
public void code() {
int a = 0;
for (int i = 0; i < 100000000; i++){
a += i;
}
System.out.println(a);
}
}
多态的应用:模板方法设计模式(TemplateMethod)
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
- 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
- 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
abstract class Template {
public final void getTime() {
long start = System.currentTimeMillis();
code();
long end = System.currentTimeMillis();
System.out.println("执行时间是:" + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template {
public void code() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
}
}
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:
- 数据库访问的封装
- Junit单元测试
- JavaWeb的Servlet中关于doGet/doPost方法调用
- Hibernate中模板程序
- Spring中JDBCTemlate、HibernateTemplate等
7、练习
编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月出现某个Employee对象的生日,则将该雇员的工资增加100元。
实验说明:
(1)定义一个Employee类,该类包含:private成员变量name,number,birthday,其中birthday 为MyDate类的对象;abstract方法earnings();
toString()方法输出对象的name,number和birthday。
(2)MyDate类包含:private成员变量year,month,day ;toDateString()方法返回日期对应的字符串:xxxx年xx月xx日
(3)定义SalariedEmployee类继承Employee类,实现按月计算工资的员工处理。该类包括:private成员变量monthlySalary;实现父类的抽象方法
earnings(),该方法返回monthlySalary值;toString()方法输出员工类型信息及员工的name, number, birthday
(4)参照SalariedEmployee类定义HourlyEmployee类,实现按小时计算工资的员工处理。该类包括:private成员变量wage和hour;实现父类的抽
象方法earnings(),该方法返回wage*hour值;toString()方法输出员工类型信息及员工的name, number, birthday。
(5)定义PayrollSystem类,创建Employee变量数组并初始化,该数组存放各类雇员对象的引用。利用循环结构遍历数组元素,输出各个对象的类
型,name,number,birthday,以及该对象生日。当键盘输入本月月份值时,如果本月是某个Employee对象的生日,还要输出增加工资信息。
提示:
//定义People类型的数组People c1[]=new People[10];
//数组元素赋值
c1[0]=new People("John","0001",20);
c1[1]=new People("Bob","0002",19);
//若People有两个子类Student和Officer,则数组元素赋值时,可以使父类类型的数组元素指向子类。
c1[0]=new Student("John","0001",20,85.0);
c1[1]=new Officer("Bob","0002",19,90.5);
import java.util.Scanner;
/**
* 编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月出现某个Employee对象的生日,则将该雇员的工资增加100元。
*
* 定义PayrollSystem类,创建Employee变量数组并初始化,该数组存放各类雇员对象的引用。
* 利用循环结构遍历数组元素,输出各个对象的类型,name,number,birthday,以及该对象生日。
* 当键盘输入本月月份值时,如果本月是某个Employee对象的生日,还要输出增加工资信息。
*
* @author: huxingxin
* @date: 2022/12/15 16:58:10
*/
public class PayrollSystem {
public static void main(String[] args) {
//创建Employee变量数组并初始化,该数组存放各类雇员对象的引用。
Employee[] employees = new Employee[5];
employees[0] = new SalariedEmployee("张三", 10001, new MyDate(1995, 10, 21), 12000);
employees[1] = new SalariedEmployee("李四", 10002, new MyDate(1996, 11, 22), 13000);
employees[2] = new SalariedEmployee("王五", 10003, new MyDate(1997, 12, 23), 14000);
employees[3] = new HourlyEmployee("赵六", 10004, new MyDate(1998, 1, 24), 50, 22.5 * 8);
employees[4] = new HourlyEmployee("孙七", 10005, new MyDate(1999, 2, 20), 70, 22.5 * 8);
//利用循环结构遍历数组元素,输出各个对象的类型,name,number,birthday,以及该对象生日
for (int i = 0; i < employees.length; i++){
System.out.print(employees[i]);
System.out.println(" 月薪资: " + employees[i].earnings());
}
System.out.println();
//当键盘输入本月月份值时,如果本月是某个Employee对象的生日,还要输出增加工资信息。
Scanner scanner = new Scanner(System.in);
System.out.print("本月: ");
int month = scanner.nextInt();
System.out.print("本日: ");
int day = scanner.nextInt();
System.out.println();
for (int i = 0; i < employees.length; i++){
Employee employee = employees[i];
MyDate birthday = employee.getBirthday();
int employeeMonth = birthday.getMonth();
int employeeDay = birthday.getDay();
System.out.print(employees[i]);
double salary;
if (month == employeeMonth && day == employeeDay){
salary = employees[i].earnings() + 100;
System.out.print(" 生日快乐, 涨薪100");
}else {
salary = employees[i].earnings();
}
System.out.println(" 月薪资" + salary);
}
}
}
/**
* MyDate类包含:private成员变量year,month,day ;toDateString()方法返回日期对应的字符串:xxxx年xx月xx日
*
* @author: huxingxin
* @date: 2022/12/15 16:47:39
*/
public class MyDate {
private int year;
private int month;
private int day;
public MyDate() {
}
public MyDate(int year, int month, int day) {
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 + "日";
}
}
/**
* 定义一个Employee类,该类包含:private成员变量name,number,birthday,其中birthday 为MyDate类的对象;
* abstract方法earnings();oString()方法输出对象的name,number和birthday。
*
* @author: huxingxin
* @date: 2022/12/15 16:50:34
*/
public abstract class Employee {
private String name;
private int number;
private MyDate birthday;
public Employee() {
}
public Employee(String name, int number, MyDate birthday) {
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;
}
/**
* 计算工资
* @return 薪资
*/
public abstract double earnings();
@Override
public String toString() {
return "name='" + name + '\'' +
", number=" + number +
", birthday='" + birthday.toDateString() + '\'';
}
}
/**
* 定义SalariedEmployee类继承Employee类,实现按月计算工资的员工处理。该类包括:private成员变量monthlySalary;
* 实现父类的抽象方法earnings(),该方法返回monthlySalary值;toString()方法输出员工类型信息及员工的name,number,birthday
*
* @author: huxingxin
* @date: 2022/12/15 16:51:46
*/
public class SalariedEmployee extends Employee{
private double monthlySalary;
public SalariedEmployee() {
}
public SalariedEmployee(double monthlySalary) {
this.monthlySalary = monthlySalary;
}
public SalariedEmployee(String name, int number, MyDate birthday, double monthlySalary) {
super(name, number, birthday);
this.monthlySalary = monthlySalary;
}
public double getMonthlySalary() {
return monthlySalary;
}
public void setMonthlySalary(double monthlySalary) {
this.monthlySalary = monthlySalary;
}
@Override
public double earnings() {
return monthlySalary;
}
@Override
public String toString() {
return "SalariedEmployee{" +
super.toString() +
'}';
}
}
/**
* 参照SalariedEmployee类定义HourlyEmployee类,实现按小时计算工资的员工处理。该类包括:private成员变量wage和hour;
* 实现父类的抽象方法earnings(),该方法返回wage*hour值;toString()方法输出员工类型信息及员工的name,number,birthday。
*
* @author: huxingxin
* @date: 2022/12/15 16:56:29
*/
public class HourlyEmployee extends Employee{
private double wage;//工资
private double hour;//月工作的小时数
public HourlyEmployee() {
}
public HourlyEmployee(double wage) {
this.wage = wage;
}
public HourlyEmployee(String name, int number, MyDate birthday, double wage, double hour) {
super(name, number, birthday);
this.wage = wage;
this.hour = hour;
}
public double getWage() {
return wage;
}
public void setWage(double wage) {
this.wage = wage;
}
@Override
public double earnings() {
return wage * hour;
}
@Override
public String toString() {
return "HourlyEmployee{" +
super.toString() +
'}';
}
}
六、接口(interface)
/**
* 接口的使用
* 1、接口使用interface来定义
* 2、Java中,接口和类是并列的两个结构
* 3、如何定义接口:定义接口中的成员
* (1)JDK7 以前,只能定义全局常量和抽象方法
* > 全局常量:public static final的,但是书写时可以省略不写
* > 抽象方法:public abstract的
*
* (2)JDK8 除了定义全局常量和抽象方法外,还可以定义静态方法、默认方法
*
* 4、接口中是不能定义构造器的!意味着接口不可以实例化
* 5、Java开发中,接口通过让类去实现的方式来使用
* 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
* 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
* 6、Java类可以实现多个接口 --> 弥补了Java单继承的局限性
* 格式:Class AA extends BB implements CC, DD, EE
* 7、接口与接口之间可以继承,而且可以多继承
* 8、接口的具体使用,体现多态性
* 9、接口,实际上可以看作是一种规范
*
* 面试题:抽象类与接口有哪些异同?
*
*
* @author: huxingxin
* @date: 2022/12/16 9:31:49
*/
public class _1InterfaceTest {
public static void main(String[] args) {
System.out.println(Flyable.MAX_SPEED);
Plane plane = new Plane();
plane.fly();
}
}
interface Flyable{
//全局常量
// public static final int MAX_SPEED = 7900; //第一宇宙速度 7.9km/s
//省略了 public static final
int MAX_SPEED = 7900; //第一宇宙速度 7.9km/s
//抽象方法
// public abstract void fly();
//省略了 public abstract
void fly();
void stop();
}
interface Attackable{
void attack();
}
/**
* 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
*/
class Plane implements Flyable{
@Override
public void fly() {
System.out.println("Plane 通过引擎起飞...");
}
@Override
public void stop() {
System.out.println("驾驶员减速停止...");
}
}
/**
* 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
*/
abstract class Kite implements Flyable{
@Override
public void fly() {
}
}
/**
* Java类可以实现多个接口 --> 弥补了Java单继承的局限性
*/
class Bullet implements Flyable, Attackable{
@Override
public void fly() {
}
@Override
public void stop() {
}
@Override
public void attack() {
}
}
1、概述
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java不支持多重继承。有了接口,就可以得到多重继承的效果。
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有is-a的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3机、手机、数码相机、移动硬盘等都支持USB连接。
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要...则必须能...”的思想。继承是一个"是不是"的关系,而接口实现则是 "能不能"的关系
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守
2、举例
接口(interface)是抽象方法和常量值定义的集合。
接口的特点:
- 用interface来定义。
- 接口中的所有成员变量都默认是由public static final修饰的。
- 接口中的所有抽象方法都默认是由public abstract修饰的。
- 接口中没有构造器。
- 接口采用多继承机制
接口定义举例
public interface Runner {
//public static final int ID = 1;
//public abstract void start();
//public abstract void run();
//public abstract void stop();
int ID = 1;
void start();
public void run();
void stop();
}
定义Java类的语法格式:先写extends,后写implements
class SubClass extends SuperClass implements InterfaceA{ }
一个类可以实现多个接口,接口也可以继承其它接口。
实现接口的类中必须提供接口中所有方法的具体实现内容,方可实、例化。否则,仍为抽象类。
接口的主要用途就是被实现类实现。(面向接口编程)
与继承关系类似,接口与实现类之间存在多态性
接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前)
,而没有变量和方法的实现
3、示例
/**
* 接口的使用
* 1、接口的使用上也满足多态性
* 2、接口,实际上就是定义了一种规范
* 3、开发中体会面向接口编程
*
* @author: huxingxin
* @date: 2022/12/16 10:51:36
*/
public class USBTest {
public static void main(String[] args) {
Computer computer = new Computer();
//1、创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
computer.transferData(new Flash());
System.out.println();
//2、创建了接口的非匿名实现类的匿名对象
computer.transferData(new Flash());
System.out.println();
//3、创建接口的匿名实现类的非匿名对象
USB phone = new USB() {
@Override
public void start() {
System.out.println("手机开始传输数据");
}
@Override
public void stop() {
System.out.println("手机结束传输数据");
}
};
computer.transferData(phone);
System.out.println();
//4、创建接口的匿名实现类的匿名对象
computer.transferData(new USB() {
@Override
public void start() {
System.out.println("mp3结束传输数据");
}
@Override
public void stop() {
System.out.println("mp3结束传输数据");
}
});
}
}
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 Computer{
public void transferData(USB usb){
usb.start();
System.out.println("具体传输数据的细节...");
usb.stop();
}
}
(1)示例1
(2)示例2
interface Runner {
public void start();
public void run();
public void stop();
}
class Person implements Runner {
public void start() {
// 准备工作:弯腰、蹬腿、咬牙、瞪眼
// 开跑
}
public void run() {
// 摆动手臂
// 维持直线方向
}
public void stop() {
// 减速直至停止、喝水。
}
}
(3)示例3
一个类可以实现多个无关的接口
interface Runner { public void run();}
interface Swimmer {public double swim();}
class Creator{public int eat(){…}}
class Man extends Creator implements Runner ,Swimmer{
public void run() {……}
public double swim() {……}
public int eat() {……}
}
与继承关系类似,接口与实现类之间存在多态性
public class Test{
public static void main(String args[]){
Test t = new Test();
Man m = new Man();
t.m1(m);
t.m2(m);
t.m3(m);
}
public String m1(Runner f) { f.run(); }
public void m2(Swimmer s) {s.swim();}
public void m3(Creator a) {a.eat();}
}
(4)示例4
interface MyInterface{
String s=“MyInterface”;
public void absM1();
}
interface SubInterface extends MyInterface{
public void absM2();
}
public class SubAdapter implements SubInterface{
public void absM1(){System.out.println(“absM1”);}
public void absM2(){System.out.println(“absM2”);}
}
实现类SubAdapter必须给出接口SubInterface以及父接口MyInterface中所有方法的实现。否则,SubAdapter仍需声明为abstract的。
4、接口的应用
(1)代理模式
概述:
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
示例:
/**
* @author: huxingxin
* @date: 2022/12/16 14:38:23
*/
public class NetWorkTest {
public static void main(String[] args) {
ProxyServer proxyServer = new ProxyServer(new 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 netWork;
public ProxyServer(NetWork netWork) {
this.netWork = netWork;
}
private void check(){
System.out.println("联网之前的检查工作");
}
@Override
public void browse() {
check();
netWork.browse();
}
}
应用场景:
安全代理:屏蔽对真实角色的直接访问。
远程代理:通过代理类处理远程方法调用(RMI)
延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
分类:
静态代理(静态定义代理类)
动态代理(动态生成代理类):JDK自带的动态代理,需要反射等知识
(2)工厂模式
《拓展:工厂设计模式.doc》
5、接口和抽象类之间的对比
在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。
6、面试题
题目一:排错
interface A {
int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX() {
System.out.println(x);
}
public static void main(String[] args) {
new C().pX();
}
}
题目二:排错
interface Playable {
void play();
}
interface Bounceable {
void play();
}
interface Rollable extends Playable, Bounceable {
Ball ball = new Ball("PingPang");
}
class Ball implements Rollable {
private String name;
public String getName() {
return name;
}
public Ball(String name) {
this.name = name;
}
public void play() {
ball = new Ball("Football");
System.out.println(ball.getName());
}
}
7、练习
定义一个接口用来实现两个对象的比较。
interface CompareObject{
public int compareTo(Object o); //若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
}
定义一个Circle类,声明redius属性,提供getter和setter方法
定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
定义一个测试类InterfaceTest,创建两个ComparableCircle对象,调用compareTo方法比较两个类的半径大小。
思 考 : 参 照 上 述 做 法 定 义 矩 形 类 Rectangle 和 ComparableRectangle 类 , 在ComparableRectangle类中给出compareTo方法的实现,比较两个矩形的面积大小。
/**
* 定义一个测试类InterfaceTest, 创建两个ComparableCircle对象, 调用compareTo方法比较两个类的半径大小。
*
* 思考: 参照上述做法定义矩形类Rectangle和ComparableRectangle类, 在ComparableRectangle类中给出compareTo方法的实现, 比较两个矩形的面积大小。
*
* @author: huxingxin
* @date: 2022/12/16 15:51:24
*/
public class InterfaceTest {
public static void main(String[] args) {
ComparableCircle circle1 = new ComparableCircle(10);
ComparableCircle circle2 = new ComparableCircle(20);
int a = circle1.compareTo(circle2);
if (a == 0){
System.out.println("circle1 == circle2");
}else if (a > 0){
System.out.println("circle1 > circle2");
}else {
System.out.println("circle1 < circle2");
}
ComparableRectangle rectangle1 = new ComparableRectangle(14, 22);
ComparableRectangle rectangle2 = new ComparableRectangle(12, 22);
int b = rectangle1.compareTo(rectangle2);
if (b == 0){
System.out.println("rectangle1 == rectangle2");
}else if (b > 0){
System.out.println("rectangle1 > rectangle2");
}else {
System.out.println("rectangle1 < rectangle2");
}
}
}
/**
* 定义一个Circle类,声明radius属性,提供getter和setter方法
*
* @author: huxingxin
* @date: 2022/12/16 15:45:47
*/
public class Circle {
private Double radius;
public Circle() {
}
public Circle(Double radius) {
this.radius = radius;
}
public Double getRadius() {
return radius;
}
public void setRadius(Double radius) {
this.radius = radius;
}
@Override
public String toString() {
return "Circle{" +
"radius=" + radius +
'}';
}
}
/**
* 定义一个接口用来实现两个对象的比较。
*
* @author: huxingxin
* @date: 2022/12/16 15:47:23
*/
interface CompareObject {
/**
*
* @param o 需要比较的对象
* @return 若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
*/
public int compareTo(Object o);
}
/**
* 定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的半径大小。
*
* @author: huxingxin
* @date: 2022/12/16 15:48:29
*/
public class ComparableCircle extends Circle implements CompareObject{
public ComparableCircle() {
}
public ComparableCircle(double radius) {
super(radius);
}
@Override
public int compareTo(Object o) {
Circle circle = (Circle) o;
// if (getRadius() > circle.getRadius()){
// return 1;
// }else if (getRadius() < circle.getRadius()){
// return -1;
// }else {
// return 0;
// }
return getRadius().compareTo(circle.getRadius());
}
}
/**
* @author: huxingxin
* @date: 2022/12/16 15:54:27
*/
public class Rectangle {
private Double length;
private Double width;
public Rectangle() {
}
public Rectangle(Double length, Double width) {
this.length = length;
this.width = width;
}
public Double getArea(){
return length * width;
}
}
/**
* @author: huxingxin
* @date: 2022/12/16 15:57:04
*/
public class ComparableRectangle extends Rectangle implements CompareObject{
public ComparableRectangle() {
}
public ComparableRectangle(double length, double width) {
super(length, width);
}
@Override
public int compareTo(Object o) {
Rectangle rectangle = (Rectangle) o;
// if (getArea() > rectangle.getArea()){
// return 1;
// }else if (getArea() < rectangle.getArea()){
// return -1;
// }else {
// return 0;
// }
return getArea().compareTo(rectangle.getArea());
}
}
8、Java 8中关于接口的改进
/**
* @author: huxingxin
* @date: 2022/12/17 17:26:31
*/
public class SubClassTest{
public static void main(String[] args) {
SubClass subClass = new SubClass();
//知识点1:接口种定义的静态方法,只能通过接口来调用
// subClass.methodA(); //Static method may be invoked on containing interface class only
CompareA.methodA();
//知识点2;通过实现类的对象,可以调用接口种的默认方法
subClass.methodB();
//如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
subClass.methodC();
//知识点3:如果子类(或实现类)继承的父类和实现类的接口中,声明了同名同参数的方法,
// 那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法 --> 类优先原则
subClass.methodD();
//知识点4:如果实现类实现了多个接口,而这多个接口定义了同名同参数的默认方法,
// 那么再实现类没有重写此方法的情况下,报错 --> 接口冲突
subClass.methodE();
subClass.myMethod();
}
}
class SubClass extends SuperClass implements CompareA, CompareB{
@Override
public void methodC() {
System.out.println("SubClass methodC");
}
@Override
public void methodE() {
System.out.println("SubClass methodE");
}
//知识点5:如何在子类(或实现类)的方法中调用父类,接口中被重写的方法
public void myMethod(){
System.out.println();
methodE();//调用自己定义的重写方法
super.methodD();//调用父类中声明的方法
//调用接口中的默认方法
CompareA.super.methodE();
CompareB.super.methodE();
}
}
/**
*
* JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
*
* @author: huxingxin
* @date: 2022/12/17 17:22:25
*/
public interface CompareA {
public static void methodA(){
System.out.println("CompareA methodA");
}
public default void methodB(){
System.out.println("CompareA methodB");
}
default void methodC(){
System.out.println("CompareA methodC");
}
default void methodD(){
System.out.println("CompareA methodD");
}
default void methodE(){
System.out.println("CompareA methodE");
}
}
/**
* @author: huxingxin
* @date: 2022/12/17 17:40:37
*/
public interface CompareB {
default void methodE(){
System.out.println("CompareB methodE");
}
}
/**
* @author: huxingxin
* @date: 2022/12/17 17:34:40
*/
public class SuperClass {
public void methodD(){
System.out.println("SuperClass methodD");
}
}
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections或者Path/Paths这样成对的接口和类。
默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java 8 API中对Collection、List、Comparator等接口提供了丰富的默认方法。
public interface AA {
double PI = 3.14;
public default void method() {
System.out.println("北京");
}
default String method1() {
return "上海";
}
public static void method2() {
System.out.println(“hello lambda!");
}
}
9、接口中的默认方法
若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:接口冲突。
解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。
若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非、抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有相同名称和参数的默认方法会被忽略。
接口冲突的解决方式
/**
* @author: huxingxin
* @date: 2022/12/17 19:17:29
*/
interface Filial {// 孝顺的
default void help() {
System.out.println("老妈,我来救你了");
}
}
interface Spoony {// 痴情的
default void help() {
System.out.println("媳妇,别怕,我来了");
}
}
class Man implements Filial, Spoony {
public static void main(String[] args) {
Man man = new Man();
man.help();
}
@Override
public void help() {
System.out.println("我该怎么办呢?");
Filial.super.help();
Spoony.super.help();
}
}
七、类的成员之五:内部类
/**
* 类的内部成员之五:内部类
* 1、Java中允许将一个类A声明在一个类B中,则类A就是内部类,类B称为外部类
* 2、内部类的分类:成员内部类(静态、非静态) vs 局部内部类(方法内、代码块内、构造器内)
* 3、成员内部类:
* 一方面:作为外部类的成员
* > 调用外部类的结构
* > 可以被static修饰
* > 可以被四种不同的权限修饰
*
*
* 另一方面:作为一个类
* > 类内可以定义属性、方法、构造器等
* > 可以被final修饰,表示此类不能被继承,言外之意,不适用final,就可以被继承
* > 可以被abstract修饰
*
* 4、关注如下的三个问题
* (1)如何实例化内部类的对象
* (2)如何在成员内部类中区分调用外部类的结构
* (3)开发中局部内部类的使用
*
* @author: huxingxin
* @date: 2022/12/17 19:52:36
*/
public class _1InnerClassTest {
public static void main(String[] args) {
//创建Brain实例(静态成员内部类)
Person.Brain brain = new Person.Brain();
brain.think();
//创建Arm实例(非静态成员内部类)
Person person = new Person("张三", 28);
Person.Arm arm = person.new Arm("大脑");
arm.raise();
arm.display("display");
}
}
class Person{
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("吃饭");
}
//静态成员内部类
static class Brain{//大脑
String name;
public Brain() {
}
public void think(){
System.out.println("大脑思考");
}
}
//非静态成员内部类
class Arm{//胳膊
String name;
public Arm() {
}
public Arm(String name) {
this.name = name;
}
public void raise(){//举起
System.out.println("举起胳膊");
// Person.this.eat(); //调用外部类的非静态属性
eat();
}
public void display(String name){
System.out.println(name);
System.out.println(this.name);
System.out.println(Person.this.name);
System.out.println("age: " + age);
}
}
public Person() {
//局部内部类
class AA{
}
}
{
//局部内部类
class BB{
}
}
public void method(){
//局部内部类
class CC{
}
}
}
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称
Inner class的名字不能与包含它的外部类类名相同
1、分类
成员内部类(static成员内部类和非static成员内部类)
局部内部类(不谈修饰符)、匿名内部类
2、成员内部类作为类的成员的角色
和外部类不同,Inner class还可以声明为private或protected;
可以调用外部类的结构
Inner class 可以声明为static的,但此时就不能再使用外层类的非static的成员变量;
3、成员内部类作为类的角色
可以在内部定义属性、方法、构造器等结构
可以声明为abstract类 ,因此可以被其它的内部类继承
可以声明为final的
编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
4、注意
- 非static的成员内部类中的成员不能声明为static的,只有在外部类或static的成员内部类中才可声明static成员。
- 外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式
- 成员内部类可以直接使用外部类的所有成员,包括私有的数据
- 当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
5、内部类举例
(1)举例1
class Outer {
private int s;
public class Inner {
public void mb() {
s = 100;
System.out.println("在内部类Inner中s=" + s);
}
}
public void ma() {
Inner i = new Inner();
i.mb();
}
}
public class InnerTest {
public static void main(String args[]) {
Outer o = new Outer();
o.ma();
}
}
(2)举例2
public class Outer {
private int s = 111;
public class Inner {
private int s = 222;
public void mb(int s) {
System.out.println(s); // 局部变量s
System.out.println(this.s); // 内部类对象的属性s
System.out.println(Outer.this.s); // 外部类对象属性s
}
}
public static void main(String args[]) {
Outer a = new Outer();
Outer.Inner b = a.new Inner();
b.mb(333);
}
}
6、如何声明局部内部类
class 外部类{
方法(){
class 局部内部类{
}
}
{
class 局部内部类{
}
}
}
7、如何使用局部内部类
只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类
但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型
8、局部内部类的特点
内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号,以及数字编号。
只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
局部内部类可以使用外部类的成员,包括私有的。
局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。
局部内部类和局部变量地位类似,不能使用public,protected,缺省,private
局部内部类不能使用static修饰,因此也不能包含静态成员
9、匿名内部类
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
(1)格式
new 父类构造器(实参列表)|实现接口(){
//匿名内部类的类体部分
}
(2)匿名内部类的特点
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用
interface A{
public abstract void fun1();
}
public class Outer{
public static void main(String[] args) {
new Outer().callInner(new A(){
//接口是不能new但此处比较特殊是子类对象实现接口,只不过没有为对象取名
public void fun1() {
System.out.println(“implement for fun1");
}
});// 两步写成一步了
}
public void callInner(A a) {
a.fun1();
}
}
10、使用
/**
* @author: huxingxin
* @date: 2022/12/17 22:36:17
*/
public class InnerClassTest {
//开发中很少见
public void method(){
//局部内部类
class AA{
}
}
//返回一个实现了Comparable接口的对象
public Comparable getComparable(){
//创建一个实现了Comparable接口的类:局部内部类
//方式一:
// class MyComparable implements Comparable{
//
// @Override
// public int compareTo(Object o) {
// return 0;
// }
// }
//
// return new MyComparable();
//方式二:
return new Comparable() {
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}
11、练习
判断输出结果为何?
public class Test {
public Test() {
Inner s1 = new Inner();
s1.a = 10;
Inner s2 = new Inner();
s2.a = 20;
Test.Inner s3 = new Test.Inner();
System.out.println(s3.a);
}
class Inner {
public int a = 5;
}
public static void main(String[] args) {
Test t = new Test();
Inner r = t.new Inner();
System.out.println(r.a);
}
}
八、面向对象内容总结
面向对象特性,是Java学习的核心、重头戏。希望大家及时地梳理、总结