static:类变量,也称为静态变量
● 什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。这个从前面的图也可看出来。
● 如何定义类变量
定义语法
访问修饰符 static 数据类型 变量名; //[推荐]
static 访问修饰符 数据类型 变量名;
● 如何访问类变量
类名.类变量名 【推荐】
或者
对象名.类变量名
假如现在定义一个类Child,在child中定义一个静态变量count:
class Child {
public static int count = 0;
}
静态变量的最大特点就是这个变量(count)能被Child类的所有的对象实例共享
● 类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问
public static void main(String[] args){
//类名.类变量名
System.out.println(A.name);
/通过对象名.类变量名
A a = new A();
System.out.println("a.name = " + a.name);
}
class A {
//类变量
public static String name = "ccc";
}
注意:
类变量的访问,必须遵守相关的访问权限,上面的name虽说是静态变量,确实能够直接调用而不去创建对象,但是必须建立在静态变量是public权限的情况下,若换成private则只能在本类调用,其他修饰符参考修饰符权限。
关于static的存放位置根据jdk的版本不同有不同的说法,比较常见的说法就是:
static的存放位置根据jdk的版本变化会有改动,但不论存放在哪都不会影响我我们的使用,而且可以肯定的一点是:静态变量被同一个类的所有对象共享,且static类变量在方法加载的时候就生成了。
类变量的细节和注意事项
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量)。
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
加上static称为类变量或静态变量,否则称为实例变量 / 普通变量 / 非静态变量
类变量可以通过 (类名.类变量名) 或者 (对象名.类变量名)来访问,但比较推荐通过 类名.类变量名 的方式访问。【前提是 满足访问修饰符的访问权限和范围】
实例变量不能通过 类名.类变量名 方式访问。
类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量。
类变量的生命周期是随类的加载开始,随着消亡而销毁。
类方法:也称静态方法
● 类方法基本介绍
类方法访问形式如下:
访问修饰符 static 数据返回类型 方法名(){ } //【推荐】
static 访问修饰符 数据返回类型 方法名(){ }
● 类方法的调用
使用方式:类名.类方法名 或者 对象名.类方法名 【前提是 满足访问修饰符的访问权限和范围】
● 类方法经典的使用场景
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。
比如:工具类中的方法 utils
Math类、Arrays类、Collections集合类。。
在开发自己使用的工具类时,可以将方法做成静态的,方便调用
class MyTools{
//求出两个数的和
public static doouble calSum(double n1, double n2){
return n1 + n2;
}
}
不创建对象调用工具类
public static void main(String[] args){
//类名.类变量名
System.out.println(MyTools.calSum(10,30));
}
类方法使用注意事项
类方法中无this的参数。
普通方法中隐含着this的参数。
public static void main(String[] args){
//静态方法,可以直接通过类名调用
D.hi();
//D.say(); 是错误的,因为say是非静态方法,需要先创建对象,在调用
new D().say();
}
class D {
public void say(){ //非静态方法,普通方法
}
public static void hi(){ //静态方法
}
}
小结:
静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员。(必须遵守访问权限)
main方法的形式: public static void main(String[] args){}
特别注意:
1)在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
2)但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例化对象后,才能通过这个对象去访问类中的非静态成员。
public class Main01{
//静态变量/属性
private static String name = "coco";
//非静态的变量/属性
private int n1 = 1008;
//静态方法
public static void hi(){
System.out.println("Main01的hi方法");
}
//非静态方法
public void cry(){
System.out.println("Main01的cry方法");
}
}
public static void main(String[] args){
//1.静态方法main可以访问本类的静态成员
System.out.println("name = " + name);
hi();
//2.静态方法main不可以访问本类的非静态成员
//System.out.println("n1 = " + n1); //错误
//cry(); //错误
//3.静态方法main 想要访问本类的非静态成员,需要先创建对象,再调用即可
Main01 main01 = new Main01();
System.out.println(main01.n1);
main01.cry();
}
● 基本介绍
代码化块又称为初始化块,属于类中的成员【即 是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类时,或创建对象时隐式调用。
● 基本语法
[修饰符]{
代码
};
注意:
1)修饰符可选,不过要写的话也只能写static
2)代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块 / 非静态代码块。
3)逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
4)" ; " 号可以写上,也可以省略。
● 代码块的好处
1)相当于另外一种形式的构造器(对构造器的补充机制),可以初始化的操作。
2)应用场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性。
● 代码块的使用
public static void main(String[] args){
Movie movie = new Movie("变形金刚");
}
class Movie{
private String name;
private double price;
private String director;
//3个构造器
//(1) 下面的三个构造器都有相同的语句
//(2) 三个构造器看起来比较冗余
//(3) 这时我们可以把相同的语句,放入到一个代码块中即可
//(4) 这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容
//(5) 代码块调用的顺序优先于构造器..
{
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始...");
}
public Movie(String name){
System.out.println("Movie(String name) 被调用...");
this.name = name;
}
public Movie(String name,double price){
this.name = name;
this.price = price;
}
public Movie(String name,double price,String director){
this.name = name;
this.price = price;
this.director = director;
}
}
调用效果
● 代码块的使用细节
1)static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着 " 类的加载 " 而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
2)类什么时候被加载
① 创建对象实例时(new)
② 创建子类对象实例,父类也会被加载
③ 使用类的静态成员时(静态属性,静态方法)
案例演示:A 类 extends B类 的静态块
3)普通的代码块,在创建对象实例时,会被隐式的调用。
被创建一次,就会调用一次。
如果只是使用类的静态成员时,普通代码块并不会执行。
4)创建一个对象时,在一个类 调用顺序是:
① 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按照他们定义的顺序调用)
② 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
③ 调用构造方法。
5)构造方法(构造器)的最前面其实隐含了 super() 和调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的。
6)如果创建一个子类时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
① 父类的静态代码块 和 静态属性(优先级一样,按定义顺序执行)
② 子类的静态代码块 和 静态属性(优先级一样,按定义顺序执行)
③ 父类的普通代码块 和 普通属性初始化(优先级一样,按定义顺序执行)
④ 父类的构造方法
⑤ 子类的普通代码块 和 普通属性初始化(优先级一样,按定义顺序执行)
⑥ 子类的构造方法
以 A extends B 为例演示
public static void main(String[] args){
//注意
//(1) 进行类的加载
//1.1 先加载 父类 A02 1.2 再加载 B02
//(2) 创建对象
//2.1 从子类的构造器开始
new B02(); //创建对象
}
class A02{ //父类
private static int n1 = getVal01();
static {
System.out.println("A02的第一个静态代码块..."); //第(2)步执行
}
{
System.out.println("A02的第一个普通代码块..."); //第(5)步执行
}
public int n3 = getVal02(); //普通属性的初始化
public static int getVal01(){
System.out.println("getVal01");//第(1)步执行
return 10;
}
public A02(){ //构造器
//隐藏的有:
//super()
//普通代码块和普通属性的初始化...
System.out.println("A02的构造器");//第(7)步执行
}
}
class B02 extends A02{ //子类
private static int n3 = getVal03();
static {
System.out.println("B02的一个静态代码块..."); //第(4)步执行
}
public int n5 = getVal04();
{
System.out.println("B02的第一个普通代码块..."); //第(9)步执行
}
public static int getVal03(){
System.out.println("getVal03");//第(3)步执行
return 10;
}
public int getVal04(){
System.out.println("getVal04");//第(8)步执行
return 10;
}
public B02(){ //构造器
//隐藏的有:
//super()
//普通代码块和普通属性的初始化...
System.out.println("B02的构造器");//第(10)步执行
}
}
运行效果
7)静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
题1:下面的代码输出什么?
class Person{
public static int total;
static{
total = 100;
System.out.println("in static block!");
}
}
public class Test{
public static void main(){
System.out.println("total = " + Person.total);
System.out.println("total = " + Person.total);
}
}
运行结果
● 什么是设计模式
● 什么是单例模式
单例(单个的实例)
饿汉式的使用步骤
public class EagerSingleton {
// 静态变量,类在创建之初就会执行实例化动作。
private static EagerSingleton instance = new EagerSingleton();
// 私有化构造函数,使外界无法创建实例
private EagerSingleton(){}
// 为外界提供获取实例接口
public static EagerSingleton getInstance(){
return instance;
}
}
饿汉式使用实例
创建一个类,GirlFriend ,但是一个人只能有一个女朋友,怎么实现?
public static void main(String[] args){
//通过方法可以获取对象
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2); //true
}
class GirlFriend{
private String name;
private static GirlFriend gf = new GirlFriend("小红红");
//如何保障我们只能创建一个 GirlFriend 对象
//步骤【单例模式-饿汉式】
//1. 将构造器私有化
//2. 在类的内部直接创建对象(该对象是static)
//3. 提供一个公共的static方法,返回 gf 对象
private GirlFriend(String name){
this.name = name;
}
public static GirlFriend getInstance(){
return gf;
}
//toString方法返回值
public String toString(){
return "GirlFriend{" +
"name =" + name + '\'' +
'}';
}
}
运行结果
注意:
饿汉式的缺点是:通常创建了对象,但是有可能没有使用它,造成资源浪费
懒汉式使用步骤
使用实例
public static void main(String[] args){
Cat instance = Cat.getInstance();
System.out.println(instance);
//再次调用getInstance
Cat instance2 = Cat.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);//
}
class Cat{
private String name;
private static Cat cat;
//步骤
//1.仍然将构造器私有化
//2.定义一个static静态属性对象
//3.提供一个public的static方法,可以返回一个Cat对象
//4.懒汉式,只有当用户使用getInstance时,才返回cat对象,
//后面再次调用时,会返回上次创建的cat对象,从而保证了单例
private Cat(String name){
this.name = name;
}
public static Cat getInstance(){
if(cat == null){ //如果还没有创建cat对象
cat = new Cat("小可爱");
}
return cat;
}
//创建toString方法
public String toString(){
return "Cat{" +
"name=" + name + '\'' +
'}';
}
}
运行效果
● 饿汉式 与 懒汉式
● 小结
final 中文意思:最后的,最终的。
final可以修饰类、属性、方法和局部变量。
在某些情况下,程序员可能有以下需求,就会使用到final:
1)当不希望类被继承时,可以用final修饰。
2)当不希望父类的某个方法被子类覆盖 / 重写(override)时,可以用final关键字修饰。
3)当不希望类的某个属性的值被修改,可以用final修饰。
4)当不希望某个局部变量被修改,可以使用final修饰。
//如果要求A类不能被其他类继承,可以使用final修饰
final class A{}
//如果要求B不能被子类C重写,使用final修饰
class AA{
public final void B(){}
}
//当不希望类的某个属性的值被修改,可以用final修饰
class E{
public final double TAX_RATE = 0.08;
}
//当不希望某个局部变量被修改,可以使用final修饰
class F{
public void hi(){
final double NUM = 0.01;
//这时,NUM也被称为局部常量
}
}
final的使用细节及注意事项
final常量赋值的三种情况
class A{
//final常量的赋值方式:
//1.在定义时赋值
public final double TAX_RATE = 0.08;
//2.在构造器中赋值
public final double TAX_RATE2;
public AA(){
TAX_RATE2 = 1.1;
}
//3.在代码块中赋值
public final double TAX_RATE3;
{
TAX_RATE3 = 5.98;
}
}
如果final修饰的属性是静态的,那么初始化的位置只能是:
1.定义时
2.在静态代码块中(不能在构造器中赋值)
class BB{
//1.在定义时赋值
public static final double TAX_RATE = 111;
//2.在静态代码块中
public static final double TAX_RATE2;
static {
TAX_RATE2 = 3.3;
}
}
注意:
虽然final修饰的普通常量和静态常量都能在被定义时以及静态代码块中赋值,但是要注意:
当final修饰的是静态常量时,不能在构造器中赋值,因为类加载比构造器早执行,所以有可能类加载好了,但是构造器并没有被执行,对象并没有创建,就会造成final所修饰的量是没有初始值的,所以编译不会通过。
final类虽然不能被继承,但是可以实例化对象
public static void main(String[] args){
CC cc = new CC(); //实例化对象
//final类虽不能比继承,但可以实例化对象
}
final class CC{}
如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承,即,仍然遵守继承的机制。
public static void main(String[] args){
new EE().cal;
}
class DD {
public final void cal(){
System.out.println("cal()方法");
}
}
class EE extends DD{}
运行展示
final的重复使用
final class AAA{
//一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。
//public final void cry(){} //错误使用
}
final 和 static 往往搭配使用,不会导致类加载,底层编译器会做优化,效率更高。
不使用 static 的情况
public static void main(String[] args){
System.out.println(BBB.num);
}
class BBB{
public static int num = 10000;
static {
System.out.println("BBB 静态代码块被执行");
}
}
运行效果
使用 static 与 final 搭配
public static void main(String[] args){
System.out.println(BBB.num);
}
class BBB{
public final static int num = 10000;
static {
System.out.println("BBB 静态代码块被执行");
}
}
运行效果
包装类(Integer、Double、Float、Boolean等都是final),String也是final类。
String类JDK源码
Double类JDK源码
题1:请编写一个程序,使用final的圆周率在3个位置分别赋值,能够计算圆形的面积。要求圆周率固定为3.14,
代码实现
public static void main(String[] args){
Circle circle = new Circle(5.0);
System.out.println("面积=" + circle.calArea());
}
private double radius;
//1.定义时赋值
//private final double PI = 3.14;
//2.构造器中赋值
private final double PI;
public Circle(double radius){
this.radius = radius;
PI = 3.14;
}
//3. 代码块中
//{
// PI = 3.14;
//}
public double calArea(){ //计算
return PI * radius * radius;
}
运行效果
题2:判断下列的代码是否有误?
public class Something{
public int addOnew(final int x){
++x;
return x + 1;
}
}
答案:++x;错误,因为++x是在x的基础上增加,而被final修饰的x值是不能被改变的。
假如我定义了一个Animal类,里面含有eat方法,但是不同的动物需求的食物是不同的,比如兔子吃草,猫吃鱼,所以所继承的eat方法就有了不确定性,这种时候,父类方法的不确定性就造成了困扰。
所以遇到这种情况,当父类的某些方法需要声明,但是又不确定如何实现时,可以将其声明为抽象方法,那么这个类就称为了抽象类。
以下面代码为例:
class Animal{
private String name;
public Animal(String name){
this.name = name;
}
public void eat(){
System.out.println("这是一只动物,但不确定要吃什么食物。");
}
}
这里的eat方法即使实现了也没有意义,
所以这里产生了父类方法不确定性的问题,
我们考虑将该方法设计为抽象类(abstract)方法。
public abstract void eat();
注意:
当一个类中存在抽象方法时,需要将该类声明为abstract类。
所以最终成型的抽象类为:
abstract class Animal{
private String name;
public Animal(String name){
this.name = name;
}
public abstract void eat();
所以当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract来修饰该类,这就被称为抽象类。
所以做个小结:
● 抽象类系统的介绍
访问修饰符 abstract 类名{
}
访问修饰符 abstract 返回类型 方法名(参数列表);//没有方法体
public class AbstractDetail01{
public static void main(){
//抽象类不能被实例化
//new A(); 会报错
}
}
abstract class A{
//抽象类可以没有abstract方法,且可以有实现的方法。
public void hi(){
System.out.println("hi");
}
}
abstract class B{ //一旦类中包含了abstract方法,则必须把这个类声明为abstract类
public abstract void hi();
}
abstract修饰属性会报错
abstract class A{
public int n1 = 10; //属性
public static String name = "coco"; //静态属性
public void hi(){ //方法
System.out.println("hi");
}
public abstract void hello(); //抽象方法
public static void ok(){ //静态方法
System.out.println("ok");
}
}
正确的使用方式:
或者直接实现E的抽象方法
思考:abstract final class A{} 能通过编译吗?
思考:abstract public static void test2(); 能通过编译吗?
思考:abstract private void test3();能通过编译吗?
编写一个Employee类,声明为抽象类,包含如下三个属性:name,id,salary。提供必要的构造器和抽象方法:work()。对于Manager类来说,他即是员工,还具有奖金(bonus)的属性。请使用继承的思想,设计CommonEmployee类和Manager类,要求类中提供必要的方法进行属性访问,实现work(),提示"经理/普通员工 名字 工作中…"。
答案:
第一题: 不能通过,因为final关键字修饰的类不能被继承
第二题: 不能通过,因为static关键字和方法重写无关(因为被static修饰的方法是静态的,静态方法随着类的加载而加载,不需要通过子类去实现,也就不具备继承的关系。)
第三题: 被private修饰的方法不能被重写
第四题代码实现
父类Employee
abstract public class Employee{
private String name;
private int id;
private double salary;
public Employee(String name,int id,double salary){
this.name = name;
this.id = id;
this.salary = salary;
}
//将work做成一个抽象方法
public abstract void work();
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;
}
}
子类 Manager 继承 父类Employee
public class Manager extends Employee{
private double bonus;
public Mnanager(String name,int id, double salary){
super(name,id,salary);
}
public double getBonus(){
return bonus;
}
public void setBouns(double bonus){
this.bonus = bonus;
}
public void work(){
System.out.println("经理 " + getName() + "工作中...");
}
}
子类CommonEmployee 继承父类Employee
public class CommonEmployee extends Employee{
public CommonEmployee(String name,int id,double salary){
super(name,id,salary);
}
public void work(){
System.out.println("普通员工 " + getName() + "工作中...");
}
}
测试类
public class Test01{
public static void main(String[] args){
//测试经理类
new Manager("jack",999,50000);
jack.setBonus(8000);
jack.work();
//测试普通员工类
new CommonEmployee("tom",888,20000);
tom.work();
}
}
● 接口是什么?
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。
从现实中看,接口就像是usb,比如电脑上的usb接口,可以接上u盘、手机、相机等等,但是你不会担心哪个插槽式专门插哪个的,因为做usb插槽的厂家和各种设备的厂家都遵守了统一的规定,包括尺寸,排线等等。
● 接口的基本语法
接口的语法:
interface 接口名{
属性
方法(1.抽象方法 2.默认实现方法 3.静态方法)
}
接口的使用:
class 类名 implements 接口{
自己属性;
自己方法;
必须实现的接口的抽象方法
}
注意:
1.在jdk7.0之前,接口里的所有方法都没有方法体。
2.jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现。
● 接口的用法介绍
例1:
AInterface接口
public interface AInterface{
//写属性
public int n1 = 10;
//写方法
//在接口中,抽象方法可以省略abstract关键字
public void hi();
//在jdk8及以后,可以有默认实现方法,但 需要使用default关键字修饰
default public void ok(){
System.out.println();
}
//在jdk8及以后,可以有静态方法
public static void cry(){
System.out.println("cry....");
}
}
Interface01类,实现上面的Ainterface接口
public class Interface01{
public static void main(String[] args){
}
}
//1.如果一个类 implement实现接口,则需要将该接口的所有抽象方法都实现
class A implements AInterface{
public void hi(){
System.out.println("hi()....");
}
}
例2:
现在有一个项目经理,管理两个程序员,功能开发一个软件,为了控制和管理软件,项目经理可以定义一些接口,然后由程序员具体实现。
实际要求:2个程序员,编写2个类,分别完成对Mysql、Oracle数据库的连接 connect 与 close
项目经理创建接口
public interface DBInterface{//项目经理
public void connect();//连接方法
public void close(); //关闭连接
}
A程序员连接mysql
public class MysqlDB implements DBInterface{ //A程序员
public void connect(){
System.out.println("连接mysql");
}
public void close(){
System.out.println("关闭mysql");
}
}
B程序员连接Oracle
public class OracleDB implement DBInterface{
public void connect(){
System.out.println("连接oracle");
}
public void close(){
System.out.println("关闭oracle");
}
}
主方法
public class Interface03{
public static void main(String[] args){
MsqlDB mysqlDB = new MysqlDB();
t(mysqlDB);
OracleDB oracleDB = new OracleDB();
t(oracleDB);
}
public static void t(DBInterface db){
db.connect();
db.close();
}
}
运行效果
接口的使用细节及注意事项
void aaa();
实际上是 abstract void aaa();
public class InterfaceDetail01{
public static void main(String[] args){
//访问接口中的属性:接口名.属性名
System.out.println(IB.n1);
}
}
interface IB{
int n1 = 10;//等价 public static final int n1 = 10;
void hi();
}
interface A extends B,C{}
● 小练
下列代码语法是否正确,如果正确,该输出什么?
interface A{
int a = 23;
}
class B implements A{
}
main函数中:
B b = new B();
System.out.println(b.a);
System.out.println(A.a);
System.out.println(B.a);
答案:
语法都对,结果输出3个23。
假如现在有一个人,他会吃饭、睡觉,有一天他有了儿子,他的儿子先天就会吃饭、睡觉,而当他儿子长大了,会学习很多的技能,比如游泳、跑步等等,而这里的吃饭睡觉就是先天的本能,这就是继承;这里的游泳跑步是后天学习的技能,这就是接口。
以代码展示:
public class TestAbstract{
public static void main(String[] args){
Son son = new Son("小宝");
son.Instinct();
son.run();
son.swimming();
}
}
class People{
private String name;
public Son(String name){
this.name = name;
}
public void Instinct(){
System.out.println(name + "人会吃饭、睡觉");
}
}
interface Swimming(){
public void swimming();
}
interface Run(){
public void run();
}
class Son extends People implements Swimming,Run{
public Son(String name){
super(name);
}
public void Swimming(){
System.out.println(this.getName() + "努力学习 会 游泳了。");
}
public void Run(){
System.out.println(this.getName() + "努力学习 会 跑步了。");
}
}
● 接口与继承区别的总结
- 接口和继承解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性。
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法。即更加的灵活。
- 接口比继承更加灵活
接口比继承更加灵活,继承是满足 is - a 的关系,而接口只需满足 like - a 的关系。
- 接口在一定程度上实现代码解耦【即:接口规范性 + 动态绑定】
1. 多态参数
在开头的usb接口介绍中,创建出一个Usb usb,既可以接收手机对象,又可以接收相机对象,就提现了接口的多态(接口引用可以指向实现了接口的类的对象)
2. 多态数组
演示一个案例:给Usb数组中,存放Phone 和 相机对象,Phone类还有一个特有的方法call(),请遍历Usb数组,如果是Phone对象,除了调用Usb接口定义的方法外,还需要调用Phone特有方法call。
代码实现
public class InterfaceTest01{
public static void main(String[] args){
//多态数组 - > 接口类型数组
Usb[] usbs = new Usb[2];
usbs[0] = new Phone_();
usbs[1] = new Camera_();
for(int i = 0; i< usbs.length;i++){
usbs[i].work();//动态绑定
//类型的向下转型
if(usbs[i] instanceof Phone_){ //判断他的运行类型
((Phone_)ubss[i]).call();
}
}
}
}
interface Usb{
void work();
}
class Phone_ implements Usb{
public void call(){
System.out.println("手机可以打电话...");
}
public void work(){
System.out.println("手机工作中...");
}
}
class Camera_ implements Usb{
public void work(){
System.out.println("相机工作中...");
}
}
3. 接口存在多态传递现象
有两个接口一个类,一个接口为IH、一个接口为IG,类名为Teacher,如果Teacher继承了IG,那么我就可以创建对象,实现IG接口,那如果我想用创建另一个对象,去实现IH接口呢?
代码实现
public class InterfaceTest02{
public static void main(String[] args){
//接口类型的变量可以指向,实现了该接口的类的对象实例
IG ig = new Teacher();
//如果IG 继承了 IH接口,而Teacher 类 实现了IG接口
//那么,实际上就相当于Teacher 类实现了IG接口。
//这就是所谓的接口多态传递现象。
IH ih = new Teacher();
}
}
interface IH{}
interface IG extends IH{}
class Teacher implements IG{
public void hi(){
}
}
从上面的代码可以看到:
如果IG 继承了 IH接口,而Teacher 类 实现了IG接口,那么实际上就相当于Teacher 类实现了IG接口,这就是所谓的接口多态传递现象。
下面代码是否有误?有错就修改,改好后输出什么结果?
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();
}
}
答案:
错误点在:
class C extends B implements A{
public void pX(){
System.out.println(x);
}
因为上面代码中有两个x,想要输出的x指代不明,修改为:
现在修改为正确的代码为:
interface A{
int x = 0;
}
class B{
int x = 1;
}
class C extends B implements A{
public void pX(){
System.out.println(A.x + " " + super.x);
}
public static void main(String[] args){
new C().pX();
}
}
类从一开始的最简方式:
class 类名{成员变量; }
再到加上成员方法的模式:
class 类名{
成员变量;
成员方法;
}
然后一步步的添加学过的内容:构造方法
class 类名{
成员变量;
构造方法;
成员方法;
}
继续加上所学的包名:
package 包名;
class 类名{
成员变量;
构造方法;
成员方法;
}
再加上继承:
package 包名;
class 类名 extends 父类{
成员变量;
构造方法;
成员方法;
}
然后加上前两章的代码块以及本章所学的接口,形成了更为完善的类结构:
package 包名;
class 类名 extends 父类{
成员变量; //属性
构造方法; //构造器
成员方法; //方法
代码块
}
类的五大成员为:
前面的四个都有了系统的学习,所以我们即将进入类的最后一个结构:内部类,也是类的重点难点。
● 什么是内部类?
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类就称为内部类(inner class)。嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员,内部类最大的特点就是可以直接访问私有属性,并且可以提现类与类之间的包含关系。
● 内部类基本语法
class Outer{ //外部类
class Inner{ //内部类
}
}
class Other{ //外部其他类
}
● 内部类的分类
定义在外部类局部位置上(比如方法内):
定义在外部类的成员位置上:
局部内部类:是定义在外部类的局部位置,比如方法中,并且有类名。
System.out.println("外部类的n2=" + 外部类名.this.n2);
小结:
● 局部内部类的使用演示
class Outer02{ //外部类
private int n1 = 100;
private void m2(){} //私有方法
public void m1(){ //方法
//1.局部内部类是定义在外部类的局部位置,通常在方法
//3.不能添加访问修饰符,但是可以使用final修饰
//4.作用域:仅仅在定义它的方法或代码中
final class Inner02{ //局部内部类(本质上仍然是一个类)
//2.可以直接访问外部类的所有成员,包含私有的
private int n1 = 800;
public void f1(){
//5.局部内部类可以直接访问外部类的成员
//如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
//则可以使用(外部类名.this.成员)访问
//Outer02.this 本质就是外部类的对象,即哪个对象调用了m1,Outer02.this就是哪个对象
System.out.println("n1=" + n1 + "外部类的n1 =" + Outer02.this.n1);
m2();
}
}
//6.外部类在方法中,可以创建Inner02对象,然后调用方法即可
Inner02 inner = new Inner02();
inner02.fi();
}
}
● 匿名内部类(重点)
匿名内部类:是定义在外部类的局部位置,比如方法中,并且没有类名
● 匿名内部类的语法
new 类或接口(参数列表){
类体
};
现在我要实现一个需求:想使用IA接口,并创建对象。
使用传统方式:写一个类,实现该接口,并创建对象
public class AnonymousInnerClass{
public static void main(String[] args){
Outer04 outer04 = new Outer04;
outer04.method();
}
}
class Outer04{ //外部类
private int n1 = 10;
public void method(){ //方法
//基于接口的匿名内部类
//1.需求:想使用IA接口,并创建对象
//2.使用传统方式,写一个类,实现该接口,并创建对象
//IA tiger = new Tiger(); 传统方式
//tiger.cry();
//3.现在的需求是 Tiger类只使用一次,且后面不再使用
//4.现在就可以使用匿名内部类来简化开发
}
}
interface IA{
public void cry();
}
class Tiger implements IA{
public void cry(){
System.out.println("老虎叫唤...");
}
}
class Dog implements IA{
public void cry(){
System.out.println("小狗汪汪叫...");
}
}
class Father{ //类
public Father(String name){ //构造器
}
public void test(){ //方法
}
}
现在有了新的需求:要求Tiger类只使用一次,且后面不再使用。
这个时候就可以使用匿名内部类来实现
public class AnonymousInnerClass{
public static void main(String[] args){
Outer04 outer04 = new Outer04;
outer04.method();
}
}
class Outer04{ //外部类
private int n1 = 10;
public void method(){ //方法
//基于接口的匿名内部类
//3.现在的需求是 Tiger类只使用一次,且后面不再使用
//4.现在就可以使用匿名内部类来简化开发
//5.tiger的编译类型 = IA
//6.tiger的运行类型 匿名内部类
/*
底层的匿名内部类实际上为:
class XXXX implements IA{
XXXX的名字是底层分配: 外部类的名字$数字,
所以这里的名字就为: Outer04$1
public void cry(){
System.out.println("老虎叫唤...");
}
}
*/
//7.jdk底层在创建匿名内部类 Outer04$1,立即马上就创建了
//Outer04$1实例,并且把地址返回给tiger
//8.匿名内部类使用一次,就不能再使用
IA tiger = new IA(){
public void cry(){
System.out.println("老虎叫唤...");
}
};
}
}
interface IA{
public void cry();
}
class Father{ //类
public Father(String name){ //构造器
}
public void test(){ //方法
}
}
public class AnonymousInnerClass{
public static void main(String[] args){
Outer04 outer04 = new Outer04;
outer04.method();
}
}
class Outer04{ //外部类
private int n1 = 10;
public void method(){ //方法
IA tiger = new IA(){
//基于接口的匿名内部类
public void cry(){
System.out.println("老虎叫唤...");
}
};
//基于类的匿名内部类
//1.father编译类型 Father
//2.father运行类型 Outer04$2
//3.底层会创建匿名内部类
/*
class Outer04$2 extends Father{
public void test(){
System.out.println("匿名内部类重写了test方法");
}
}
*/
//4.同时也直接返回了 匿名内部类 Outer04$2
//5.注意("jack")参数列表会传递给 构造器
Father father = new Father("jack"){
public void test(){
System.out.println("匿名内部类重写了test方法");
}
};
System.out.println("father对象的运行类型=" + father.getClass()); //Outer04$2
father.test();
//基于抽象类的匿名内部类
new Animal = new Animal(){
void eat(){
System.out.println("小狗吃骨头...");
}
};
animal.eat();
}
}
interface IA{
public void cry();
}
class Father{ //类
public Father(String name){ //构造器
}
public void test(){ //方法
}
}
abstract class Animal{ //抽象类
abstract void eat();
}
1. 匿名内部类的语法比较奇特,因为匿名内部类即是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对面前代码分析可以看出这个特点,因此可以调用匿名内部类方法。
new Person(){
public void hi(){
System.out.println("匿名内部类重写了hi方法。");
}
public void ok(String str){
super.ok(str);
}
}.ok("jack"); //}.hi(); hi也可以调用
class Person{
public void hi(){
System.out.println("Person hi()");
}
public void ok(String str){
System.out.println("Person ok()" + str);
}
}
运行效果
2. 可以直接访问外部类的所有成员,包括私有的。
class Outer{
private int n1 = 99;
public void f1(){
//创建一个基于类的匿名内部类
Person p = new Person(){
public void hi(){
//可以直接访问外部类的所有成员,包括私有
System.out.println("匿名内部类重新了hi方法" + n1);
}
};
p.hi();//动态绑定,运行类型是 Oter$1
}
}
运行效果
3. 不能添加访问修饰符,因为它的地位就是一个局部变量。
4. 作用域:仅仅在定义它的方法或代码块中。
5. 匿名内部类 – 访问 --> 外部类成员 【访问方式:直接访问】
6. 外部其他类 – 不能访问 --> 匿名内部类(因为匿名内部类相当于一个局部变量)
7.如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
成员内部类:定义在外部类的成员位置,并且没有static修饰
特点:成员内部类,是定义在外部类的成员位置上。
● 成员内部类的使用
1. 可以直接访问外部类的所有成员,包含私有的。
public class MemberInnerClass{
public static void main(String[] args){
Outer outer = new Outer();
outer.t1();
}
}
class Outer{ //外部类
private int n1 = 10;
public String name = "张三";
class Inner{ //成员内部类
public void say(){
//可以直接访问外部类的所有成员,包含私有的。
//n1就是外部类的私有属性,name是公有属性
System.out.println("n1 = " + n1 + "name = " + name);
}
}
//写一个方法使用成员内部类
public void t1(){
//使用成员内部类
Inner inner = new Inner();
inner.say();
}
}
运行效果
2. 可以添加任意访问修饰符(public、protected、默认、private),因为它相当于一个成员
受保护的内部类
class Outer{ //外部类
private int n1 = 10;
public String name = "张三";
protected class Inner{ //成员内部类
public void say(){
//可以直接访问外部类的所有成员,包含私有的。
//n1就是外部类的私有属性,name是公有属性
System.out.println("n1 = " + n1 + "name = " + name);
}
}
}
3. 作用域:和外部类的其他成员一样,为整个类体,比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法。
4. 成员内部类 — 访问 —> 外部类成员(比如:属性)【访问方式:直接访问】
5. 外部类 — 访问 —>成员内部类 【访问方式:创建对象,再访问】
6. 外部其他类 — 访问 —>成员内部类 【有两种访问方式】
第一种:
//相当于把new Inner() 当做是outer成员
Outer.Inner inner = outer.new Inner();
inner.say();
第二种:
//在外部类中,编写一个方法,可以返回Inner对象
//该方法,返回一个Inner实例
Outer.Inner innerInstance = outer.getInnerInstance();
inner08Instance.say();
public Inner getInnerInstance(){
return new Inner();
}
7. 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类.this.成员)去访问
public class Inner{ //成员内部类
private int n1 = 66;
public void say(){
//可以直接访问外部类的所有成员,包含私有的
//如果成员内部类的成员和外部类的成员重名,会遵守就近原则。
//可以通过 外部类名.this.属性 来访问外部类的成员
System.out.println("外部类的n1=" + outer.this.n1);
}
}
运行效果
静态内部类:静态内部类是定义在外部类的成员位置,并且有static修饰
● 静态内部类的结构
class Outer{ //外部类
private int n1 = 10;
private int n2 = 10;
//Inner10就是静态内部类
//1.放在外部类的成员位置
//2.使用static修饰
static class Inner10{
}
}
● 静态内部类的使用
1. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
class Outer{ //外部类
private int n1 = 10;
private static String name = "张三";
//Inner10就是静态内部类
//3.可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
static class Inner10{
public void sau(){
System.out.println(name);
}
}
}
2. 可以添加任意访问修饰符(public、protected、默认、private),因为它相当于一个成员
//Inner10就是静态内部类
//4.可以添加任意访问修饰符(public、protected、默认、private),因为它相当于一个成员
private static class Inner10{ //最前面的private可以换成任意的访问修饰符
public void sau(){
System.out.println(name);
}
}
3. 作用域:同其他的成员,为整个类体
4. 静态内部类 — 访问 —> 外部类(比如:静态属性)【访问方式:直接访问所有静态成员】
5. 外部类 — 访问 —> 静态内部类 访问方式:创建对象,再访问
6. 外部其他类 — 访问 —> 静态内部类
7. 如果外部类和静态内部类的成员重名时,静态内部类访问的时候,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
class Outer{
private int n1 = 10;
private static String name = "张三";
private static void cry(){}
static class Inner{
private static String name = "李四";
public void say(){
//如果外部类和静态内部类的成员重名时,静态内部类访问的时候,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
System.out.println(name); //输出的结果为李四
//如果想要访问外部类的name,就使用外部名.成员
System.out.println("外部类name = " + Outer.name);
}
}
}
● 内部类小结:
1. 内部类有四种:局部内部类,匿名内部类,成员内部类,静态内部类
2. 成员内部类,静态内部类,是放在外部类的成员位置,本质就是一个成员。
3. 重点掌握的匿名内部类语法为:
new 类/接口(参数列表){
//.....
};
分析下面代码输出的结果为多少?
public class Test{ //外部类
public Test(){ //构造器
Inner s1 = new Inner();
s1.a = 10;
Inner s2 = new Inner();
System.out.println(s2.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);
}
}
答案为5,5
Inner r = t.new Inner();//5
System.out.println(r.a);//5