文章目录
参考文献
1. 关键字:static
-
当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过 new 关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。
-
我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份。
例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
-
如果想让一个类的所有实例共享数据,就用类变量!
1.1 类属性、类方法的设计思想
-
类属性作为该类各个对象之间共享的变量。在设计类时,分析哪
些属性不因对象的不同而改变,将这些属性设置为类属性
。相应
的方法设置为类方法
。 -
如果方法与调用者无关,则这样的方法通常被声明为
类方法
,由
于不需要创建对象就可以调用类方法,从而简化了方法的调用。 -
使用范围:
在Java类中,可用static
修饰属性、方法、代码块、内部类 -
被修饰后的成员具备以下特点:
1.随着类的加载而加载
2.优先于对象存在
3.修饰的成员,被所有对象所共享
4.访问权限允许时,可不创建对象,直接被类调用 -
示例
package pers.chh3213.classLearn20211229;
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);
}
}
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();
System.out.println(Circle.name);
}
}
1.2 类变量
- 类变量(类属性)由该类的所有实例共享
- 举例
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 = "张继科";
}
}
//中国人
class Chinese{
String name;
int age;
static String nation;
}
1.2.1 类变量 vs 实例变量内存解析
1.3 类方法
-
没有对象的实例时,可以用
类名.方法名()
的形式访问由static修饰的类方法。 -
在
static
方法内部只能访问类的static
修饰的属性或方法,不能访问类的非static
的结构。 -
非静态的方法中,可以调用所有的方法或属性
package pers.chh3213.classLearn20211229; class Person { private int id; private static int total = 0; public static int getTotalPerson() { // id++;//非法 return total; } public Person() { total++; id = total; } } 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()); } }
输出为:
Number of total is 0 Number of total is 1
-
因为不需要实例就可以访问
static
方法,因此static
方法内部不能有this
和super
。 -
static
修饰的方法不能被重写package pers.chh3213.classLearn20211229; class Person { private int id; private static int total = 0; public static int getTotalPerson() { // id++;//非法 return total; } public static void setTotalPerson(int total) { // this.total = total;//非法,在static方法中不能有this或super Person.total=total; } public Person() { total++; id = total; } } public class PersonTest { public static void main(String[] args) { System.out.println("Number of total is " + Person.getTotalPerson()); //没有创建对象也可以访问静态方法 Person p1 = new Person(); Person.setTotalPerson(10); System.out.println( "Number of total is "+ Person.getTotalPerson()); } }
输出为:
Number of total is 0
Number of total is 10
开发中,如何确定一个属性是否需要声明 static 的?
- 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
- 类中的常量也常常声明为 static
开发中,如何确定一个方法是否要声明为 static 的?
- 操作静态属性的方法,通常设置为
static
的 - 工具类中的方法,习惯上声明为
static
的。比如:Math、Arrays、Collections
1.4 练习
编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额” ,定义封装这些属性的方法。账号要自动生成。
编写主类,使用银行账户类,输入、输出3个储户的上述信息。考虑:哪些属性可以设计成static属性。
package pers.chh3213.classLearn20211229;
class Bank{
private int id;
private String password;
private double balance;
private static double annualInterestRate;
private static double min_balance;
public Bank(int id,String password,double balance,double annualInterestRate,double min_balance) {
// TODO Auto-generated constructor stub
this.id =id;
this.balance=balance;
Bank.annualInterestRate=annualInterestRate;
Bank.min_balance=min_balance;
}
public Bank() {
id =(int)( Math.random()*30);
balance =(double)( Math.random()*30000+5000);
password = "1245679";
Bank.annualInterestRate=1;
Bank.min_balance=10;
// TODO Auto-generated constructor stub
}
public void setBalance(double balance) {
this.balance = balance;
}
public void setId(int id) {
this.id = id;
}
public static void setAnnualInterestRate(double annualInterestRate) {
Bank.annualInterestRate = annualInterestRate;
}
public static void setMin_balance(double min_balance) {
Bank.min_balance = min_balance;
}
public void setPassword(String password) {
this.password = password;
}
public double getBalance() {
return balance;
}
public int getId() {
return id;
}
public static double getMin_balance() {
return min_balance;
}
public static double getAnnualInterestRate() {
return annualInterestRate;
}
public String getPassword() {
return password;
}
public void printInfo() {
System.out.println("id:"+id);
System.out.println("password:"+password);
System.out.println("balance:"+balance);
System.out.println("annualInterestRate:"+annualInterestRate);
System.out.println("min_balance:"+min_balance);
}
}
public class BankTest {
public static void main(String[] args) {
Bank[] bank = new Bank[3];
for (Bank bank2 : bank) {
bank2 = new Bank();
bank2.setAnnualInterestRate(0.2);
bank2.setMin_balance(1);
bank2.printInfo();
System.out.println("==============");
}
}
}
1.5 补充:JAVA静态方法是否可以被继承?
静态方法可以继承但没有表现出多态性。根据《java编程思想》中的描述这是因为静态方法和静态属性没有采用动态绑定。具体表现就是,将子类实例向上转型则会调用到父类中的静态方法和属性,不转型就调用子类自身的静态方法和属性。编译器不推荐通过实例去调用静态方法和属性,因为这种调用方式容易造成混淆。
java中静态属性和静态方法可以被继承,但是没有被重写(overwrite)而是被隐藏.
原因:
1). 静态方法和属性是属于类的,调用的时候直接通过类名.方法名
完成,不需要继承机制即可以调用。如果子类里面定义了对应的静态方法和属性,那么这时候父类的静态方法或属性称之为"隐藏"。如果你想要调用父类的静态方法和属性,直接通过父类名.方法
或变量名
完成,至于是否继承一说,子类是有继承静态方法和属性,但是跟实例方法和属性不太一样,存在"隐藏"的这种情况。
2). 多态之所以能够实现,依赖于继承、接口和重写、重载(继承和重写最为关键)。有了继承和重写就可以实现父类的引用指向不同子类的对象。重写的功能是:"重写"后子类的优先级要高于父类的优先级,但是“隐藏”是没有这个优先级之分的。
3). 静态属性、静态方法和非静态的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。
1.5 单例(Singleton)设计模式
-
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模式免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。======》》”套路”
-
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例。并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为 private,这样,就不能用 new 操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法以返回类内部创建的对象,静态方法只能访问类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的。
1.5.1 单例模式的实现方式:饿汉式
public class SingletonTest {
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.内部创见类的对象
//4.要求此对象也必须声明为静态的
private static Bank instance = new Bank();
//3.提供公共的静态的方法,返回类的对象。
public static Bank getInstance(){
return instance;
}
}
1.5.2 单例模式的实现方式:懒汉式
public class SingletonTest2 {
public static void main(String[] args) {
Order order1 = Order.getInstance();
Order order2 = Order.getInstance();
System.out.println(order1 == order2);//true
}
}
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;
}
}
1.5.3 饿汉式v.s.懒汉式
-
饿汉式:
1.坏处:对象加载时间过长。
2.好处:饿汉式是线程安全的。 -
懒汉式:
1.好处:延迟对象的创建。
2.坏处:目前的写法,会线程不安全。—》到多线程内容时,再修改
1.5.4 单例模式的优点
- 由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
- 举例
1.5.5 单例模式的应用场景
- 网站的计数器,一般也是单例模式实现,否则难以同步。
- 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
- 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
- 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
- Application也是单例的典型应用
- Windows 的**Task Manager (任务管理器)**就是很典型的单例模式
- Windows 的 **Recycle Bin(回收站)**也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
2. 理解main方法的语法
-
由于 Java 虚拟机需要调用类的 main()方法,所以该方法的访问权限必须是
public
,又因为 Java 虚拟机在执行main()
方法时不必创建对象,所以该方法必须是static
的,该方法接收一个 String 类型的数组参数,该数组中保存执行 Java 命令时传递给所运行的类的参数。 -
又因为
main()
方法是静态的,我们不能直接访问该类中的非静态成员
,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
2.1 main()方法的使用说明
main()
方法作为程序的入口;main()
方法也是一个普通的静态方法;main()
方法也可以作为我们与控制台交互的方式(之前使用 Scanner)。
package pers.chh3213.classLearn20211229;
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]);
}
}
}
2.2 命令行参数用法举例
2.3 面试题
回答:
正常编译,但是无法成功运行
3. 类的成员之四:代码块(初始化块)
3.1 代码块的作用及分类
- 代码块(或初始化块)的作用:
对Java类或对象进行初始化 - 代码块(或初始化块)的分类:
一个类中代码块若有修饰符,则只能被static
修饰,称为静态代码块(static block),没有使用static
修饰的,为非静态代码块。 - static代码块通常用于初始化static的属性
class Person {
public static int total
static {
total = 100;/为total赋初值
}
...//其它属性或方法声明
}
3.2 静态代码块 vs. 非静态代码块
-
静态代码块:用static修饰的代码块
1,可以有输出语句。
2,可以对类的属性、类的声明进行初始化操作。
3,不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
4,若有多个静态的代码块,那么按照从上到下的顺序依次执行。
5,静态代码块的执行要优先于非静态代码块。
6,静态代码块随着类的加载而加载,且只执行一次。 -
非静态代码块:没有static修饰的代码块
1,可以有输出语句。
2,可以对类的属性、类的声明进行初始化操作。
3,除了调用非静态的结构外,还可以调用静态的变量或方法。
4·若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
5,每次创建对象的时候,都会执行一次。且先于构造器执行。 -
示例
package pers.chh3213.classLearn20211230; 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(){ } //static 的代码块 static{ System.out.println("hello,static block-1"); //调用静态结构 desc = "我是一个爱小说的人"; info(); //不能调用非静态结构 // eat(); // name = "Tom"; } static{ System.out.println("hello,static block-2"); } //非 static 的代码块 { System.out.println("hello,block-2"); } { System.out.println("hello,block-1"); //调用非静态结构 age = 1; eat(); //调用静态结构 desc = "我是一个爱小说的人 1"; info(); } //方法 public Person(String name,int age){ this.name = name; this.age = age; } 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-1 我是一个快乐的人。 hello,static block-2 我是一个爱小说的人 hello,block-2 hello,block-1 吃饭 我是一个快乐的人。 hello,block-2 hello,block-1 吃饭 我是一个快乐的人。 1
3.3 程序中成员变量赋值的执行顺序
静态初始化块由父类到子类的顺序依次执行
-
示例1
package pers.chh3213.classLearn20211230; //总结:由父类到子类,静态先行 public class LeafTest{ public static void main(String[] args){ new Leaf(); } } 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 的构造器"); } }
输出为:
Root 的静态初始化块 Mid 的静态初始化块 Leaf 的静态初始化块 Root 的普通初始化块 Root 的无参数的构造器 Mid 的普通初始化块 Mid 的无参数的构造器 Mid 的带参数构造器,其参数值:尚硅谷 Leaf 的普通初始化块 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(); } }
输出为:
11111111111 44444444444 77777777777 ************************ 22222222222 33333333333 55555555555 66666666666 ************************ 22222222222 33333333333 55555555555 66666666666 ************************ 22222222222 33333333333
4. 关键字:final
4.1 说明
- 在Java中声明类、变量和方法时,可使用关键字
final
来修饰,表示“最终的” 。 final
修饰的类不能被继承。提高安全性,提高程序的可读性。- VString类、System类、StringBuffer类
final
修饰的方法不能被子类重写。- 比如: Object类中的getClass()。
final
修饰的变量(成员变量或局部变量)即称为常量。名称大写,且只能被赋值一次。-
final
标记的成员变量必须在声明时或在每个构造器中或代码块中显式赋值,然后才能使用。 -
final
修饰局部变量:尤其是使用final
修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。 一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
-
4.2 应用举例
4.2.1 final 修饰类
final class A{
}
class B extends A{
}//错误,不能被继承。
4.2.2 final 修饰方法
class A {
public final void print() {
System.out.printin("A");
}
}
class B extends A {
public void print() {//错误,不能被重写。
System.out.println("尚硅谷");
}
}
4.2.3 final修饰变量–常量
class A {
private final string INFO ="atguigu"; //声明常量
public void print() {
//The final field A. INFO cannot be assigned
//INFO ="尚硅谷";
}
}
- 常量名要大写,内容不可修改。
static final
:全局常量
4.2.4 面试题找错
面试题1
public class Something {
public int addOne(final int x) {
return ++x; // return x + 1;
}
}
编译错误!常量形参一旦被赋值后就不能再赋值
面试题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++;
System.out.println(o.i);
}
}
class Other {
public int i;
}
编译和执行都正确,输出为:1
5. 抽象类与抽象方法
- 随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
5.1 abstract关键字
-
用
abstract
关键字来修饰一个类,这个类叫做抽象类。 -
用
abstract
来修饰一个方法,该方法叫做抽象方法。- 抽象方法:只有方法的声明,没有方法的实现。以分号结束:
比如:public abstract void talk();
- 抽象方法:只有方法的声明,没有方法的实现。以分号结束:
-
含有抽象方法的类必须被声明为抽象类。
-
抽象类不能被实例化。抽象类是用来被继承的,抽象类的子类必须重
写父类的抽象方法,并提供方法体。若没有重写全部的抽象方法,仍
声明自身为抽象类。 -
不能用
abstract
修饰变量、代码块、构造器; -
不能用
abstract
修饰私有方法、静态方法、final的方法、final的类。- 原因很简单,就是私有方法、静态方法、final的方法虽然可以被继承,但不能被重写;final的类不能被继承
-
示例
package pers.chh3213.classLearn20211230; public class AbstractTest { public static void main(String args[]) { A a = new B(); a.m1(); a.m2(); } } abstract class A{ abstract void m1(); public void m2() { System.out.println("A类中ЕXtm2方法"); } } class B extends A{ void m1() { System.out.println("B类中定义的m1方法"); } }
5.2 抽象类应用
-
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
-
示例
卡车(Truck)和驳船(RiverBarge)的燃料效率和行驶距离的计算方法完全不同。Vehicle 类不能提供计算方法,但子类可以。
/* Java 允许类设计者指定:超类声明一个方法但不提供实现,该方法的实现由子类提 供。这样的方法称为抽象方法。有一个或更多抽象方法的类称为抽象类。 * Vehicle 是一个抽象类,有两个抽象方法。 * 注意:抽象类不能实例化 new Vihicle()是非法的 */ 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( ) { //写出计算驳船行驶距离的具体方法 } }
-
思考
1.问题1:为什么抽象类不可以使用final关键字声明?
答:final修饰的类不能被继承2.问题2:一个抽象类中可以定义构造器吗?
答:可以。抽象类只是不能被实例化。3.问题3:是否可以这样理解:抽象类就是比普通类多定义了抽象方 法,除了不能直接进行类的实例化操作之外,并没有任何的不同?
答:是的
5.3 创建抽象类的匿名子类对象
- 使用目的:只会使用一次的类,就不用新建一个类。
- 示例
-
父类 抽象类——Person
abstract class Person { //其他方法及变量定义 ..... //抽象方法 public abstract void eat(); }
-
创建Person类的匿名子类对象
//method 为已创建的一个方法
method(new Student)//非匿名类的匿名对象
Student stu = new Student();
method(stu);//非匿名的类的非匿名对象
//创建了一匿名类的对象,对象名为p
Person p = new Person{
//覆盖(实现)父类Person中的抽象方法
@Override
public void eat(){
//方法体
}
}
//调用匿名子类对象
method(p);
- 创建Person类的匿名子类的匿名对象
method(new Person{
//覆盖Person类中的抽象类方法
@Override
public void eat(){
//具体实现的方法体
}
})
- 详细实现
- Num类
package pers.chh3213.classLearn20211230;
public class Num {
}
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(){
// System.out.println("人吃饭");
// }
//抽象方法
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("学生应该呼吸新鲜的无雾霾空气");
}
}
- PersonTest类
package pers.chh3213.classLearn20211230;
/*
* 抽象类的匿名子类
*
*/
public class PersonTest {
public static void main(String[] args) {
method(new Student()); //匿名对象
Worker worker = new Worker();
method1(worker); //非匿名的类非匿名的对象
method1(new Worker()); //非匿名的类匿名的对象
System.out.println("*********************");
//创建了一个匿名子类的对象:p
Person p = new Person(){
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void breath() {
System.out.println("呼吸空气");
}
};
method1(p);
System.out.println("**********************");
//创建匿名子类的匿名对象
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.walk();
}
public static void method(Student s){
}
}
class Worker extends Person{
@Override
public void eat() {
}
@Override
public void breath() {
}
}
5.4 多态的应用:模板方法设计模式(TemplateMethod)
-
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
-
当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
-
换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
-
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:
- 数据库访问的封装
- Junit单元测试
- JavaWeb的Servlet中关于doGet/doPost方法调用
- Hibernate中模板程序
- Spring中JDBCTemlate, HibernateTemplate等
-
示例1
package pers.chh3213.classLearn20211230; public class TemplateMethodTest { public static void main(String[] args) { Template template = new SubTemplate(); template.getTime(); } } 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 { @Override public void code() { for (int i=0; i < 10000; i++) { 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 万美元!!"); } }
5.5 练习
编写工资系统,实现不同类型员工(多态)的按月发放工资。如果当月出现某个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对象的生日,还要输出增加工资信息。
-
Employee.java
package pers.chh3213.classLearn20211230; public abstract class Employee { private String name; private int number; private MyDate birthday; public abstract double earnings(); public MyDate getBirthday() { return birthday; } public String getName() { return name; } public int getNumber() { return number; } public void setBirthday(MyDate birthday) { this.birthday = birthday; } public void setName(String name) { this.name = name; } public void setNumber(int number) { this.number = number; } public Employee(String name,int number,MyDate birthday) { // TODO Auto-generated constructor stub this.name = name; this.number = number; this.birthday=birthday; } public Employee() { // TODO Auto-generated constructor stub } public String toString() { return name+" "+number+" "+birthday.toDateString(); } } class MyDate{ private int year; private int month; private int day; public int getDay() { return day; } public int getMonth() { return month; } public int getYear() { return year; } public void setDay(int day) { this.day = day; } public void setMonth(int month) { this.month = month; } public void setYear(int year) { this.year = year; } public MyDate(int year,int month, int day) { // TODO Auto-generated constructor stub this.year=year; this.month=month; this.day=day; } public String toDateString() { return year+"年"+month+"月"+day+"日"; } } class SalariedEmployee extends Employee{ private double monthlySalary; public double getMonthlySalary() { return monthlySalary; } public void setMonthlySalary(double monthlySalary) { this.monthlySalary = monthlySalary; } public SalariedEmployee(String name,int number,MyDate birthday,double monthlySalary) { super(name,number,birthday); this.monthlySalary = monthlySalary; } public double earnings() { return monthlySalary; } public String toString() { return "SalariedEmployee "+super.toString(); } } class HourlyEmployee extends Employee{ private double wage; private double hour; public double getHour() { return hour; } public double getWage() { return wage; } public void setHour(double hour) { this.hour = hour; } public void setWage(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 earnings() { return wage*hour; } public String toString() { return "HourlyEmployee "+super.toString(); } }
-
PayrollSystem.java
package pers.chh3213.classLearn20211230; import java.util.Scanner; public class PayrollSystem { public static void main(String[] args) { Employee[] employees = new Employee[3]; employees[0] = new SalariedEmployee("jack", 0, new MyDate(1997, 1,1),10000); employees[1] = new SalariedEmployee("rose", 1, new MyDate(1997, 8,8),10001); employees[2] = new HourlyEmployee("mary", 2, new MyDate(1996, 8,8),50,5); System.out.println("请输入当前月份:"); Scanner scanner = new Scanner(System.in); int month = scanner.nextInt(); for (Employee employee : employees) { // System.out.println(employee.earnings()); System.out.println(employee.toString()); double salary = employee.earnings(); System.out.println("月工资为:" + salary); if (month==employee.getBirthday().getMonth()) { System.out.println("工资增加100元"); } } } }
6. 接口(interface)
-
一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java 不支持多重继承。有了接口,就可以得到多重继承的效果。
-
另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有 is-a 的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3 机、手机、数码相机、移动硬盘等都支持 USB 连接。
-
接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个"是不是"的关系,而接口实现则是"能不能"的关系。
-
接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。
6.1 接口的特点
-
接口(interface)是抽象方法和常量值定义的集合。
-
接口的特点:
- 用
interface
来定义。 - 接口中的所有成员变量都默认是由
public static final
修饰的。 - 接口中的所有抽象方法都默认是由
public abstract
修饰的。 - 接口中没有构造器。意味着接口不可以实例化。
- 接口采用多继承机制
- 用
-
接口的声明
[可见度] interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 抽象方法
}
接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
6.2 接口与类
- Java 开发中,接口通过让类去实现(
implements
)的方式来使用。定义Java类的语法格式:先写extends
,后写implements
class SubClass extends SuperClass implements InterfaceA{}
- 一个类可以实现多个接口,接口也可以继承其它接口。
- 实现接口的类中必须提供接口中**所有方法的具体实现内容,**方可实例化。否则,仍为抽象类。
- 接口的主要用途就是被实现类实现。(面向接口编程)
- 与继承关系类似,接口与实现类之间存在多态性
- 接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义(JDK7.0及之前),而没有变量和方法的实现。
重写接口中声明的方法时,需要注意以下规则:
类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
如果实现接口的类是抽象类,那么就没必要实现该接口的方法。
在实现接口的时候,也要注意一些规则:
一个类可以同时实现多个接口。
一个类只能继承一个类,但是能实现多个接口。
一个接口能继承另一个接口,这和类之间的继承比较相似。
6.2.1 接口与类相似点
- 一个接口可以有多个方法。
- 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
- 接口的字节码文件保存在 .class 结尾的文件中。
- 接口相应的字节码文件必须在与包名称相匹配的目录结构中。
6.2.2 接口与类的区别
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
6.2.3 抽象类和接口的区别
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是
public static final
类型的。 - 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
注:JDK 1.8 以后,接口里可以有静态方法和方法体了。
注:JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。更多内容可参考 Java 8 默认方法。
注:JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。更多内容可参考 Java 9 私有接口方法。
6.3 接口应用举例
- 举例1
-
举例2
-
举例3
-
举例4
-
举例5
package pers.chh3213.classLearn20211231;
/*
* 接口的使用
* 1.接口使用上也满足多态性
* 2.接口,实际上就是定义了一种规范
* 3.开发中,体会面向接口编程!
*
*/
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("打印机结束工作");
}
}
6.4 接口的应用:代理模式(Proxy)
- 概述
代理模式是 Java 开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
- 应用场景
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。
比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有 100MB,在打开文件时,不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用 proxy 来进行大图片的打开。
- 分类
- 静态代理(静态定义代理类)
- 动态代理(动态生成代理类)
JDK 自带的动态代理,需要反射等知识
6.5 接口的应用:工厂模式
6.6 练习
面试题:排错
package pers.chh3213.classLearn20211231;
public class FindWrong extends B implements A {
public void px() {
System.out.println(x);
}
public static void main(String[] args) {
new FindWrong().px();
}
}
interface A {
int x=0;
}
class B {
int x=1;
}
编译报错:The field x is ambiguous,x指代不清。
面试题:排错
interface Playable {
void play();
}
interface Bounceable {
void play();
}
interface Rollable extends Playable, Bounceable {
Ball ball= new Ball("PingPang"); //省略了 public static final
}
public 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"); //The final field Rollable.ball cannot be assigned
System.out.println(ball.getName());
}
}
编译报错: ball = new Ball("Football"); //The final field Rollable.ball cannot be assigned
- InterfaceTest.java
package pers.chh3213.classLearn20211231;
interface CompareObject {
public int compareTo(Object o);//返回值为0,代表相等;若为正数,代表当前对象大,负数代表当前对象小
}
class Circle{
private Double radius;
public Double getRadius() {
return radius;
}
public void setRadius(Double radius) {
this.radius = radius;
}
public Circle(Double radius) {
super();
this.radius=radius;
}
public Circle() {
super();
}
}
class ComparableCircle extends Circle implements CompareObject{
public ComparableCircle(double radius) {
super(radius);
}
public int compareTo(Object o) {
if(this==o)return 0;
else if (o instanceof ComparableCircle) {
ComparableCircle circle = (ComparableCircle) o;
//正确的方式一:
// if(this.getRadius() > circle.getRadius()){
// return 1;
// }else if(this.getRadius() < circle.getRadius()){
// return -1;
// }else{
// return 0;
// }
// 当属性 radius 声明为 Double 类型时,可以调用包装类的方法
//正确的方式二:
return this.getRadius().compareTo(circle.getRadius());
}else{
return 0;
// throw new RuntimeException("传入数据类型不匹配");
}
}
}
public class InterfaceTest{
public static void main(String[] args) {
ComparableCircle c1 = new ComparableCircle(1.0);
ComparableCircle c2 = new ComparableCircle(1.1);
int result = c1.compareTo(c2);
System.out.println(result);
int compareValue1 = c1.compareTo(new String("AA"));
System.out.println(compareValue1);
}
}
注意:使用第2种方式时,Circle类中用的类型是 Double,而不是double。
instanceof的使用不要忘了。
6.7 Java 8 中关于接口的改进
- Java 8 中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
- 静态方法:使用
static
关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像 Collection/Collections 或者 Path/Paths 这样成对的接口和类。 - 默认方法:使用
default
关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java 8 API 中对 Collection、List、Comparator 等接口提供了丰富的默认方法。
6.7.1 静态方法与 默认方法说明
- 若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法) ,在实现类同时实现了这两个接口时,会出现:接口冲突。
解决办法:实现类必须覆盖接口中同名同参数的方法,来解决冲突。 - 若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则。接口中具有相同名称和参数的默认方法会被忽略。
- 接口中定义的静态方法,只能通过接口来调用。
接口名称.静态方法名称()
- 通过实现类的对象,可以调用接口中的默认方法。如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法。
- 如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
- 调用接口中被重写的方法:
接口.super.方法
- 调用父类被重写的方法:
super.方法
- 调用接口中被重写的方法:
练习:接口冲突的解决方式
// Filial.super.help();
// Spoony.super.help();
ManTest.java
package pers.chh3213.classLearn20211231;
public class ManTest {
public static void main(String[] args) {
Man man = new Man();
man.help();
}
}
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();
// }
}
7. 类的成员之五:内部类
- 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
- 在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
- 内部类一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。
Inner class的名字不能与包含它的外部类类名相同 - 分类:
成员内部类(static
成员内部类和非static
成员内部类)
局部内部类(方法内、代码块内、构造器内)(不谈修饰符)、匿名内部类 - 成员内部类作为类的成员的角色:
- 和外部类不同, Inner class还可以声明为
private
或protected
; - 可以调用外部类的结构
- Inner class可以声明为
static
的,但此时就不能再使用外层类的非static
的成员变量
- 和外部类不同, Inner class还可以声明为
- 关注如下的3个问题
- 如何实例化成员内部类的对象
- 如何在成员内部类中区分调用外部类的结构
- 开发中局部内部类的使用
7.1 成员内部类(static成员内部类和非static成员内部类)
-
成员内部类作为类的角色:
- 可以在内部定义属性、方法、构造器等结构
- 可以声明为
abstract
类,因此可以被其它的内部类继承 - 可以被
final
修饰,表示此类不能被继承。言外之意,不使用final
,就可以被继承 - 编译以后生成OuterClass$InnerClass.class字节码文件(也适用于局部内部类)
-
【注意】
1,非static
的成员内部类中的成员不能声明为static
的,只有在外部类或static
的成员内部类中才可声明static
成员。即静态内部类可以有静态成员,而非静态内部类则不能有静态成员。
2,外部类访问成员内部类的成员,需要内部类.成员
或内部类对象.成员
的方式
3,成员内部类可以直接使用外部类的所有成员,包括私有的数据
4,当想要在外部类的静态成员部分使用内部类时,可以考虑内部类声明为静态的
5.静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量 -
如何创建静态成员内部类和非静态成员内部类的对象?
如一个Person
类中有static Dog
和Bird
两个成员内部类,则创建方式分别为Person.Dog dog = new Person.Dog();//1 Person p = new Person();//2 Person.Bird bird = p.new Bird();
7.1.1 举例
- 举例1
package pers.chh3213.classLearn20211231;
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();
}
}
输出为:在内部类Inner中s=100
- 举例2:如何在成员内部类中调用外部类的结构
package pers.chh3213.classLearn20211231;
public class OuterClass {
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(OuterClass.this.s); //外部类对象属性s
}
}
public static void main(String[] args) {
OuterClass a = new OuterClass();
OuterClass.Inner b= a.new Inner();
b.mb(333);
}
}
输出为:
333
222
111
- 举例3–判断输出结果是什么
package pers.chh3213.classLearn20211231;
public class InnerTest {
public InnerTest() {
Inner s1 = new Inner();
s1.a = 10;
Inner s2 = new Inner();
s2.a = 20;
InnerTest.Inner s3 = new InnerTest.Inner();
System.out.println(s3.a);
}
class Inner {
public int a = 5;
}
public static void main(String[] args) {
InnerTest t = new InnerTest();
Inner r =t.new Inner();
System.out.println(r.a);
}
}
输出为:
5
5
7.2 局部内部类
- 局部内部类声明
class外部类{
方法(){
class局部内部类(
}
}
{
class局部内部类{
}
}
}
-
如何使用局部内部类
- 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类
- 但是它的对象可以通过外部方法的返回值返回使用,返回值类型只能是局部内部类的父类或父接口类型
-
局部内部类的特点
- 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的
.class
文件,但是前面冠以外部类的类名和$符号,以及数字编号
。 - 只能在声明它的方法或代码块中使用,而且是先声明后使用。除此之外的任何地方都不能使用该类。
- 局部内部类可以使用外部类的成员,包括私有的。
- 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内部类和局部变量的声明周期不同所致。
- 局部内部类和局部变量地位类似,不能使用
public
,protected
,缺省
,private
- 局部内部类不能使用
static
修饰,因此也不能包含静态成员
- 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的
-
使用注意
public class InnerClassTest { //开发中应用场景示例 // public void onCreate(){ // // int number = 10; // // View.OnClickListern listener = new View.OnClickListener(){ // // public void onClick(){ // System.out.println("hello!"); // System.out.println(number); // } // // } // // button.setOnClickListener(listener); // //} /* * 在局部内部类的方法中(比如:show)如果调用局部内部类所声明的方法(比如:method)中的局部变量(比如:num)的话, * 要求此局部变量声明为final的。 * * jdk 7及之前版本:要求此局部变量显式的声明为final的 * jdk 8及之后的版本:可以省略final的声明 * */ public void method(){ //局部变量 int num = 10; class AA{ public void show(){ // num = 20; //Local variable num defined in an enclosing scope must be final or effectively final System.out.println(num); } } } }
7.2.1 举例
public class InnerClassTest1 {
// 开发中很少见
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;
}
};
}
}
7.3 匿名内部类
-
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
-
匿名的内部类是没有名字的内部类。不能extends(继承) 其它类,但一个内部类可以作为一个接口,由另一个内部类实现
-
格式:
new 父类构造器(实参列表) |实现接口(){ //匿名内部类的类体部分 }
-
匿名内部类的特点
- 匿名内部类必须继承父类或实现接口
- 匿名内部类只能有一个对象
- 匿名内部类对象只能使用多态形式引用
7.3.1 举例
-
举例1
-
举例2
package pers.chh3213.classLearn20211231; /** * 1.匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。 * 一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。 * * 2.格式: * new 父类构造器(实参列表)|实现接口(){ * //匿名内部类的类体部分 * } * * 3.匿名内部类的特点 * > 匿名内部类必须继承父类或实现接口 * > 匿名内部类只能有一个对象 * > 匿名内部类对象只能使用多态形式引用 */ interface Product{ public double getPrice(); public String getName(); } public class AnonymousTest{ public void test(Product p){ System.out.println("购买了一个" + p.getName() + ",花掉了" + p.getPrice()); } public static void main(String[] args) { AnonymousTest ta = new AnonymousTest(); //调用test方法时,需要传入一个Product参数, //此处传入其匿名实现类的实例 ta.test(new Product(){ public double getPrice(){ return 567.8; } public String getName(){ return "AGP显卡"; } }); } }
7.4 Java内部类、外部类之间访问权限
摘抄自参考资料4。
7.4.1 内部类可以访问外部类的所有成员吗
在不考虑static
的情况下
package pers.chh3213.classLearn20220101exercise;
public class A {
String a = "a";
private String privateA = "privateA";
protected String protectedA = "protectedA";
public String publicA = "publicA";
public static void main(String[] args){
A a = new A();
a.test();
}
public void test(){
B b = new B();
b.accessA();
}
class B{
public void accessA(){
System.out.println(a);
System.out.println(privateA);
System.out.println(protectedA);
System.out.println(publicA);
}
}
}
结果为:
a
privateA
protectedA
publicA
可以看出内部类B可以访问所有外部类A的成员。
7.4.2 外部类能访问内部类的所有成员吗
创建如下代码:
public class A {
public static void main(String[] args){
A a = new A();
a.accessB();
}
public void accessB(){
B b = new B();
System.out.println(b.b);
System.out.println(b.privateB);
System.out.println(b.protectedB);
System.out.println(b.publicB);
}
class B{
String b = "b";
private String privateB = "privateB";
protected String protectedB = "protectedB";
public String publicB = "publicB";
public void accessA(){
System.out.println(a);
System.out.println(privateA);
System.out.println(protectedA);
System.out.println(publicA);
}
}
}
执行结果如下
b
privateB
protectedB
publicB
各种权限都能访问
7.4.3 匿名内部类与外部类访问权限
/**
* 匿名内部类访问权限测试
*/
public class A {
String a = "a";
private String privateA = "privateA";
protected String protectedA = "protectedA";
public String publicA = "publicA";
public static void main(String[] args){
A a = new A();
a.test();
}
public void test(){
B b = new B(){
@Override
public void accessA(){
//匿名内部类访问外部类
System.out.println(a);
System.out.println(privateA);
System.out.println(protectedA);
System.out.println(publicA);
}
};
b.accessA();
accessB(b);
}
public void accessB(B b){
//外部类访问内部类
System.out.println(b.b);
System.out.println(b.privateB);
System.out.println(b.protectedB);
System.out.println(b.publicB);
}
class B{
String b = "b";
private String privateB = "privateB";
protected String protectedB = "protectedB";
public String publicB = "publicB";
public void accessA(){
}
}
}
输出结果为:
a
privateA
protectedA
publicA
b
privateB
protectedB
publicB
可见,匿名内部类和内部类访问权限都一样。
7.4.4 若考虑静态内部类的情况
-
静态的匿名内部类:
public class A { String a = "a"; private String privateA = "privateA"; protected String protectedA = "protectedA"; public String publicA = "publicA"; public static void main(String[] args){ A a = new A(); a.test(); } public void test(){ B b = new B(){ @Override public void accessA(){ //匿名内部类访问外部类 System.out.println(a); System.out.println(privateA); System.out.println(protectedA); System.out.println(publicA); } }; b.accessA(); accessB(b); } public void accessB(B b){ //外部类访问内部类 System.out.println(b.b); System.out.println(b.privateB); System.out.println(b.protectedB); System.out.println(b.publicB); } static class B{ String b = "b"; private String privateB = "privateB"; protected String protectedB = "protectedB"; public String publicB = "publicB"; public void accessA(){ } } }
输出结果为: a privateA protectedA publicA b privateB protectedB publicB
可见,与匿名内部类一样,可以正常访问。
-
普通静态内部类
public class A { String a = "a"; private String privateA = "privateA"; protected String protectedA = "protectedA"; public String publicA = "publicA"; public static void main(String[] args){ A a = new A(); a.test(); a.accessB(); } public void test(){ B b = new B(); b.accessA(); } public void accessB(){ B b = new B(); System.out.println(b.b); System.out.println(b.privateB); System.out.println(b.protectedB); System.out.println(b.publicB); } static class B{ String b = "b"; private String privateB = "privateB"; protected String protectedB = "protectedB"; public String publicB = "publicB"; public void accessA(){ System.out.println(a); System.out.println(privateA); System.out.println(protectedA); System.out.println(publicA); } } }
输出结果报错: Exception in thread "main" java.lang.Error: Unresolved compilation problems: Cannot make a static reference to the non-static field a Cannot make a static reference to the non-static field privateA Cannot make a static reference to the non-static field protectedA Cannot make a static reference to the non-static field publicA at pers.chh3213.classLearn20220101exercise.A$B.accessA(A.java:35) at pers.chh3213.classLearn20220101exercise.A.test(A.java:19) at pers.chh3213.classLearn20220101exercise.A.main(A.java:13)
原因:
内部类创建的时候会自动持有一个外部类的引用,所以创建内部类B的对象可以直接访问A的成员,而外部类A的对象要手动创建一个内部类B的对象才能访问B对象的成员。但加上static
关键字后A的成员就访问不了,加上static
关键字后,B就脱离了对象,B不会持有A的引用了,但B还可以创建A对象,去访问对象的所有成员
在B中修改如下,即可正常输出:
static class B{
public void accessA(){
A aObj = new A();
System.out.println(aObj.a);
System.out.println(aObj.privateA);
System.out.println(aObj.protectedA);
System.out.println(aObj.publicA);
}
}
7.4.5 总结
- 内部类创建的时候会自动持有一个外部类的引用,所以创建内部类B的对象可以直接访问A的成员,而外部类A的对象要手动创建一个内部类B的对象才能访问B对象的成员。但加上
static
关键字后A的成员就访问不了,因为加上static
关键字后,B就脱离了对象,B不会持有A的引用了,但B还可以创建A对象,去访问对象的所有成员。 - 综上,外部类、内部类之间通过对象可以访问任意权限的成员。
8. 练习
- 接口是否可继承接口? 抽象类是否可实现(implements)接口? 抽象类是否可继承实体类(concrete class)?
答案是: 接口可以继承接口。抽象类可以实现(implements)接口,
抽象类可继承实体类,但实体类必须不能是如下两种情况之一:
1,final修饰符修饰的类是不能的
2,如果此实体类有且仅有私有的构造函数也是不能的。
- 以下代码是否也可以编译通过
interface A{
int x = 0;
}
class B{
int x =1;
}
class C extends B implements A {
public void pX(){
System.out.println(x); //super.x A.x
}
public static void main(String[] args) {
new C().pX();
}
}
答案:错误。在编译时会发生错误(错误描述不同的JVM有不同的信息,意思就是未明确的x调用,两个x都匹配(就象在同时import java.util和java.sql两个包时直接声明Date一样)。对于父类的变量,可以用super.x来明确,而接口的属性默认隐含为 public static final.所以可以通过A.x来明确。
interface A{}
class B implements A{
public String func(){
return "func";
}
}
class Demo{
public static void main(String[] args){
A a=new B();
System.out.println(a.func());
}
}
编译失败:因为A接口中并未定义func方法。
-
编一个程序,包含以下文件
(1)Shape.java文件,在该文件中定义接口类Shape,该接口在shape包中。
属性:PI。
接口:求面积的方法area()。
(2)Circle.java文件,在该文件中定义圆类Circle,该类在circle包中,实现Shape接口类。
属性:圆半径radius。
方法:构造器;实现求面积方法area();求周长方法perimeter()。
(3)Cylinder.java文件,在该文件中定义圆柱体类Cylinder,该类在cylinder包中,继承圆类。
属性:圆柱体高度height。
方法:构造器;求表面积方法area();求体积方法volume()。
(4)X5_3_6.java文件,在该文件中定义主类X5_3_6,该类在默认包中,其中包含主方法main(),在主方法中创建两个圆类对象cir1和cir2,具体尺寸自己确定,并显示圆的面积和周长;再创建两个圆柱体类的对象cy1和cy2,具体尺寸自己确定,然后分别显示圆柱体cy1和cy2的底圆的面积和周长以及它们各自的体积和表面积。
【编程分析】本题主要考察接口、包、继承、封装等问题。-
shape.java
package pers.chh3213.classLearn20211231exercise; public interface Shape { public double PI=3.14; public double area() ; }
-
Circle.java
package pers.chh3213.classLearn20211231exercise; public class Circle implements Shape{ private double radius; public Circle (double radius ) { this.radius = radius; } public double area() { return PI*radius*radius; } public double perimeter() { return 2*PI*radius; } }
-
Cylinder.java
package pers.chh3213.classLearn20211231exercise; public class Cylinder extends Circle { private double height; public Cylinder(double height,double radius) { super(radius); this.height=height; } @Override public double area() { return super.area()*2+super.perimeter()*height; } public double volume() { return super.area()*height; } }
-
X5_3_6.java
package pers.chh3213.classLearn20211231exercise; public class X5_3_6 { public static void main(String[] args) { Circle cir1 = new Circle(5.0); Circle cir2 = new Circle(4.0); System.out.println(cir1.area()); System.out.println(cir1.perimeter()); System.out.println(cir2.area()); System.out.println(cir2.perimeter()); Cylinder cy1 = new Cylinder(10.0, 1.0); Cylinder cy2 = new Cylinder(20.0, 1.0); System.out.println(cy1.area()); System.out.println(cy1.volume()); System.out.println(cy2.area()); System.out.println(cy2.volume()); } }
-
-
Anonymous Inner Class (匿名内部类) 是否可以extends(继承)其它类,是否可以implements(实现)interface(接口)?
答:匿名的内部类是没有名字的内部类。不能extends(继承) 其它类,但一个内部类可以作为一个接口,由另一个内部类实现
-
Static Nested Class(嵌套类) 和 Inner Class(内部类)的不同
答:Nested Class (一般是C++的说法),Inner Class (一般是JAVA的说法)。
Java内部类与C++嵌套类最大的不同就在于是否有指向外部的引用上。
注: 静态内部类(Inner Class)意味着
1.创建一个static内部类的对象,不需要一个外部类对象,2.不能从一个static内部类的一个对象访问一个外部类对象 -
补足代码
interface Test { void func(); } class Demo { public static void main(String[] args) { // 补足代码;(匿名内部类) // new Demo().show(new Test() { // public void func() { // // } // }); } void show(Test t) { t.func(); } }
-
有一个Car类,有属性temperature(温度),车内有Air(空调),有吹风的功能flow,Air会监视车内的温度,如果温度超过40度则吹冷气。如果温度低于0度则吹暖气,如果在这之间则关掉空调。实例化具有不同温度的Car对象,调用空调的flow方法,测试空调吹的风是否正确
package pers.chh3213.classLearn20211231exercise; class Car{ private double temperature; public Car(double temperature) { // TODO Auto-generated constructor stub this.temperature = temperature; } class Air{ public void flow() { if(temperature>40) { System.out.println("cold wind"); } else if(temperature<0) { System.out.println("warm wind"); } else { System.out.println("close Air"); } } } public void test(Air air) { air.flow(); } } public class CarTest { public static void main(String[] args) { Car c1 = new Car(45); Car c2 = new Car(15); Car c3 = new Car(-45); c1.test(c1.new Air()); c2.test(c2.new Air()); c3.test(c3.new Air()); } }
-
有一个铃声接口类Bell,有一个接口sound,有一个手机类Cellphone,具有闹钟功能alarmclock,参数是Bell类型,测试手机类的闹钟功能,通过匿名内部类作为参数,打印:懒猪起床了。
package pers.chh3213.classLearn20220101exercise; interface Sound { public void print(); } public class CellPhone { public void alarmClock(Bell bell) { bell.print(); } class Bell implements Sound{ public void print() {}; } public static void main(String[] args) { CellPhone phone = new CellPhone(); phone.alarmClock(phone.new Bell() { public void print() { System.out.println("getup"); } }); } }
-
编译通过
public class Something {
public static void main(String[] args) {
Something s = new Something();
System.out.println("s.doSomething() returns " + doSomething());
}
public String doSomething() {
return "Do something ...";
}
}
看上去很完美。
答案: 错。看上去在main里call doSomething没有什么问题,毕竟两个methods都在同一个class里。
但仔细看,main是static的。static method不能直接call non-static methods。
可改成"System.out.println("s.doSomething() returns " + s.doSomething());"。
同理,static method不能访问non-static instant variable。
- 是否可以通过编译?
class Something {
final int i;
public void doSomething() {
System.out.println("i = " + i);
}
}
答案: 错。final int i是个final的instant variable (实例变量,或叫成员变量)。
final的instant variable没有default value,必须在constructor (构造器)结束之前被赋予一个明确的值。
可以修改为"final int i = 0;"。
- 以下代码运行结果
package pers.chh3213.classLearn20220101exercise;
public class Test {
static int x, y, z;
static {
int x = 5;
x--;
}
static {
x--;
}
public static void main(String[] args) {
System.out.println("x=" + x);
z--;
method();
System.out.println("result:" + (z + y + ++z));
}
public static void method() {
y = z++ + ++z;
}
}
x=-1
result:3