[TOC]
Java面向对象初级
一、方法的重载
注意事项和使用细节
- 方法名必须相同;
- 形参列表必须不同(形参类型、个数或顺序至少有一个不同,参数名无要求);
- 返回类型无要求。
可变参数:public int sum(int ... nums)
表示可接收多个参数。
可变参数本质为数组;
参数为0个或多个;
实参可以为数组;
可以与普通参数放在同一形参列表,但必须放在最后;
一个形参列表中只能有一个可变参数。
二、作用域
- 全局变量有默认值,可以不用赋值,局部变量无默认值,需要赋值。
- 属性可以与局部变重名,使用时遵循就近一致原则。
三、构造器
构造器是初始化对象,并非创建对象。
一个类可以定义多个不同的构造器,即构造器重载。
对象创建流程:
对于以下代码:
1 2 3 4 5 6 7 8 9 10
| class Person{ int age = 90; String name; Person(String n,int a){ name = n; age = a; } } Preson p1 = new Person("小倩",20);
|
- 加载Person类的信息(
Person.class
),只加载一次;
在堆中分配空间(地址);
完成对象初始化:
默认初始化(age=0,name=null)
显式初始化(age=90,name=null)
构造器初始化(age=20,name=小倩)
- 把对象在堆中的地址返回给p1(引用)
四、封装
封装的作用
- 提高代码的安全性;
- 提高代码的复用性;
- “高内聚”:封装细节,便于修改内部代码,提高可维护性;
- “低耦合”:简化外部调用,便于调用者使用,便于扩展和写作。
封装主要代码
get()
和set()
函数,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package demo;
import java.util.Scanner;
public class test1 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); person p = new person(); p.setName("小明"); p.setAge(600); System.out.println(p.getName()); System.out.println(p.getAge()); } } class person { private String name; private int age;
public String getName() { return name; } public void setName(String name) { if(name.length() < 2 || name.length() > 6) { System.out.println("名字长度不符合要求,默认张三!"); this.name = "张三"; } else { this.name = name; } } public int getAge() { return age; } public void setAge(int age) { if(age > 120) { System.out.println("年龄不符合要求,默认18!"); this.age = 18; } else { this.age = age; } } }
|
封装与构造器
- 构造器可破解封装中的数据校验,所以可进行下列操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package demo; import java.util.Scanner; public class test1 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); person p = new person(); p.setName("小明"); p.setAge(600); System.out.println(p.getName()); System.out.println(p.getAge()); System.out.println("**********************"); person p1 = new person("小明",400); System.out.println(p1.getName()); System.out.println(p1.getAge()); } } class person { private String name; private int age; public person() { } public person(String name, int age) { this.name = name; this.age = age; setName(name); setAge(age); } public String getName() { return name; } public void setName(String name) { if(name.length() < 2 || name.length() > 6) { System.out.println("名字长度不符合要求,默认张三!"); this.name = "张三"; } else { this.name = name; } } public int getAge() { return age; } public void setAge(int age) { if(age > 120) { System.out.println("年龄不符合要求,默认18!"); this.age = 18; } else { this.age = age; } } }
|
五、继承
创建子类对象时,不论使用子类的那个构造器,默认情况下都会调用父类的无参构造器,若父类没有提供无参构造器,则必须在子类的构造器中用super去指定父类中的构造器(如super(String name,int age);
)以完成父类的初始化,否则编译不通过。
若希望指定去调用父类的某个构造器时,需要显式的调用一下:super(参数列表)
。
super()
在使用时,必须放在构造器第一行(this()
也是),故两方法不能共存于同一个构造器中。
super()
和this()
的区别:
方法重载overload
和方法重载override
的比较:
- 重载发生在子类,方法名一样,形参类别、个数或顺序至少有一个不同,返回类型和修饰符无要求;
- 重写发生在父子类,方法名一样,形参类别、个数或顺序都相同,子类重写的方法的返回类型和父类返回类型一致或是其子类,子类的权限不能小于父类。
六、多态
方法的多态
对象的多态
- 一个对象的编译类型(
"="左边
)和运行类型("="右边
)可以不一致。
多态的向上转型
1 2 3
| Animal animal = new Cat(); Object obj = new Cat(); animal.eat();
|
多态的向下转型
语法:子类类型 引用名 = (子类类型) 父类引用;
只能强转父类的引用,不能强制父类的对象(对象创建后不再改变);
当向下转型后,可以调用子类类型中的所有成员;
要求父类的当前引用必须指向的是当前目标类型的对象。
1 2 3
| Animal animal = new Cat(); Cat cat = (Cat) animal; Dog dog = (Dog) animal;
|
属性的“重写”
- 属性无“重写”之说,属性只看编译类型,即“=”左边。
1 2 3 4 5 6 7 8
| A a = new B(); System.out.print(a.age); class A{ int age = 10; } class B extend A{ int age = 20; }
|
instenceof
比较操作符,用于判断对象的运行类型是否为xx类型或xx类型的子类型。
1 2 3 4 5 6 7 8 9 10 11 12
| B b = new B(); System.out.print(b instenceof B); System.out.print(b instenceof A);
A a = new B(); System.out.print(a instenceof B); System.out.print(a instenceof A);
Object obj = new Object(); System.out.print(obj instenceof A); class A{} class B extend A{}
|
动态绑定机制
当调用对象方法时,该方法会与该对象的内存地址/运行类型绑定;
(即若调用的方法子类没有而父类有,则先找到父类方法执行,但此时在父类方法中又需要执行子类和父类都有的方法时,需要看该对象的运行类型是哪个类,就执行那个类中的方法。重名的方法和对象的运行类型动态绑定)
当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用。
多态数组
1 2 3 4 5 6 7 8 9 10
| Person[] persons = new Person[3]; persons[0] = new Person("Jack",20); persons[1] = new Teacher("Jack",20); persons[2] = new Student("Jack",20);
for(int i = 0;i < persons.length;i++) { persons.say(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| Person[] persons = new Person[3]; persons[0] = new Person("Jack",20); persons[1] = new Teacher("Jack",20); persons[2] = new Student("Jack",20);
for(int i = 0;i < persons.length;i++) { if(persons[i] instanceof Student){ Student student = (Student)persons[i]; student.study(); } }
|
多态参数
- 方法定义的形参参数类型为父类类型,实参类型可以为子类类型]
1 2 3
| Student student = new Syudent(); getName(student); void getName(Person p){}
|
七、Object类详解
equals
方法
==
和equals
的对比
==
既可以判断基本类型,又可以判断引用类型;
==
若判断基本类型(如比较数值大小时),判断的是值是否相等;
==
若判断引用类型(如比较两对象时),判断的是地址是否相等,即判断是否为同一个对象;
equals
是Object类中的方法,只能判断引用类型;
equals
默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等。
举例:Person类中equals方法的重写
Person类中的equals重写,判断两对象地址或属性是否一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package demo; public class test1 { public static void main(String[] args) { Person p1 = new Person("a",10); Person p2 = new Person("a",10); System.out.println(p1.equals(p2)); } } class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public boolean equals(Object obj) { if (this == obj) { return true; } if(obj instanceof Person) { Person p = (Person) obj; return p.name.equals(this.name) && p.age == this.age; } return false; } }
|
hashCode
hashCode
的几点小结
- 提高具有哈希结构的容器的效率;
- 两个引用,如果指向的时同一个对象,则哈希值肯定是一样的,否则不一样;
- 哈希值主要根据地址号来来的,不能完全将哈希值等价于地址;
hashCode
也会重写。
toString
方法
- 默认返回:全类名+@+哈希值的16进制,子类往往重写
toString
方法,用于返回对象的属性信息;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package demo;
public class test1 { public static void main(String[] args) { Person p1 = new Person("a",10); System.out.println(p1.toString()+" "+p1.hashCode()); } } class Person { private String name; private int age;
public Person(String name, int age) { this.name = name; this.age = age; } }
|
- 重写
toString
方法,打印对象或者拼接对象时,都会自动调用该对象的toString
形式;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| package demo; public class test1 { public static void main(String[] args) { Person p1 = new Person("a",10); System.out.println(p1.toString()+" "+p1.hashCode()); } } class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
|
- 当直接输出一个对象时,
toString方
法会被默认调用。(如:System.out.println(Person);
就会默认调用Person.toString()
)
1 2 3
| Person p1 = new Person(); System.out.println(p1);
|
Java面向对象高级
一、 类变量和类方法
类变量(静态变量)
定义语法:访问修饰符 static 数据类型 变量名
(推荐)或static 访问修饰符 数据类型 变量名
;
访问方法:类名.类变量名
(推荐)或者对象名.类变量名
;
类变量是被类声明的所有对象共享的,实例对象是对象独有的;
类变量在类加载的时候生成,即对象未创建是也可使用;
jdk8以前类变量存放在方法区,jdk8以后类变量放在堆里;
类方法(静态方法)
定义语法:访问修饰符 static 数据返回类型 方法名(){}
(推荐)或static 访问修饰符 数据返回类型 方法名(){}
;
调用方法:类名.类方法名
(推荐)或者对象名.类方法名
;
使用场景:将一些通用的方法设计成静态类,可以不创建对象就调用相关方法,如Math.sqrt()
;
类方法和普通方法都是随着类的加载而加载,将结构信息储存在方法区,但类方法中无this()
,普通方法中隐含this()
;
类方法不允许使用和对象有关的关键字,如super()
和this()
,普通方法可以;
静态方法只能访问静态成员,而普通成员方法,既可以访问普通变量(方法),也可以访问静态变量(方法)。
二、main
方法语法
解释main
方法的形式:public static void main(String[] args){}
main
方法是虚拟机调用;
- Java虚拟机需要调用类的
main()
方法,所以该方法的访问权限必须是public
;
- Java虚拟机在执行
main()
方法时不必创建对象,所以方法必须是static
;
- 该方法接收String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数,即
java 执行的程序 参数1 参数2 参数3 ...
。
有以下代码:
1 2 3 4 5 6 7 8
| public class test1 { public static void main(String[] args) { for (int i = 0;i < args.length;i++) { System.out.println("第" + (i+1) + "个参数为:" + args[i]); } } }
|
在命令行编译执行观察参数:

三、代码块
- 代码块相当于另一种形式的构造器(对构造器的补充机制),可做初始化的操作;
- 代码块先于构造器加载;
- 静态代码块只在类加载时执行一次,普通代码块每创建一次对象就执行一次;
- 当只使用某个类的静态成员(未创建该类对象)时,该类的普通代码块不会被执行,但只要加载类时,静态代码块都会执行一次;
- 类什么时候加载:
- 创建对象实例时(new)
- 创建子类对象实例,父类会被加载,即先加载父类(及其代码块或构造器),再加载子类(及其代码块或构造器)
- 使用类的静态成员时(静态属性、静态成员)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package demo;
public class test1 { public static void main(String[] args) { A a = new A(12); System.out.println("======================="); A b = new A("Hello"); } } class A { int a; String b; static { System.out.println("静态代码块只执行一次!"); } { System.out.println("输出公共代码。"); System.out.println("代码块无论放在哪个位置,都优先加载。"); System.out.println("普通代码块每创建一次对象就执行一次!"); }; public A(int a) { this.a = a; System.out.println(a);
}
public A(String b) { this.b = b; System.out.println(b);
} }
|
四、类中调用的顺序(重点补充)
- 在继承的类中,构造器的前面其实隐含了
super()
和普通代码块,故调用顺序为父类中的代码块->父类中的构造器->子类中的代码块->子类中的构造器
,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| package demo;
public class test1 { public static void main(String[] args) { B b = new B(); } }
class A { { System.out.println("A的代码块被调用。"); } public A() { System.out.println("A的构造器被调用。"); } } class B extends A { { System.out.println("B的普通代码块被调用。"); } public B() { System.out.println("B的构造器被调用。"); } }
|
创建一个对象时,在一个类中调用的顺序是:
- 调用静态代码块和静态属性初始化(两者优先级相同,若有多个,则按照定义顺序调用)
- 普通代码块和普通属性的初始化(两者优先级相同,若有多个,则按照定义顺序调用)
- 调用构造器方法
创建一个子类对象时(继承关系),调用顺序是:
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性(优先级一样,按定义顺序执行)
- 父类的构造方法
- 子类的普通代码块和普通属性(优先级一样,按定义顺序执行)
- 子类的构造方法
补充:静态代码块只能调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员(包括静态成员)。
五、单例设计模式
设计模式:是在大量的实践中总结和理论化后优选的代码结构、编程风格以及解决问题的思考方式。
单例设计模式:采取一定的方法保证在整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
单例模式有两种方式:饿汉式和懒汉式(都要保证对象的唯一性)
饿汉式(线程安全)
- 饿汉式在类加载时创建对象实例;
- 饿汉式是不论是否要用某个对象,先将该对象创建好,故可能会出现多余对象,造成资源的浪费;
- 饿汉式线程安全。
懒汉式(线程不安全)
- 懒汉式在使用对象时才会创建对象实例;
- 懒汉式是先定义唯一的对象,然后在调用对象时再创建对象,不会出现多余对象浪费资源的情况;
- 懒汉式线程不安全。
演示代码SingleTon.java
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
| package CodeBase.SingleTon;
class SingleTon_1 { String name; private static SingleTon_1 singleTon_1 = new SingleTon_1("饿汉式"); private SingleTon_1(String name) { this.name = name; } public static SingleTon_1 getInstance() { return singleTon_1; } }
class SingleTon_2 { String name; private static SingleTon_2 singleTon_2; private SingleTon_2(String name){ this.name = name; }; public static SingleTon_2 getInstance() { if(singleTon_2 == null) { singleTon_2 = new SingleTon_2("懒汉式"); } return singleTon_2; } } public class SingleTon { public static void main(String[] args) { System.out.println("=============正常情况下============"); SingleTon_Test singleTon_test = new SingleTon_Test(); singleTon_test.singleTon1_test(); System.out.println("=========================="); singleTon_test.singleTon2_test(); } }
class SingleTon_Test { void singleTon1_test() { SingleTon_1 test_singleTon_1_1 = SingleTon_1.getInstance(); SingleTon_1 test_singleTon_1_2 = SingleTon_1.getInstance(); System.out.println(test_singleTon_1_1.name); System.out.println("饿汉式对象一地址:" + test_singleTon_1_1); System.out.println("饿汉式对象二地址:" + test_singleTon_1_2); } void singleTon2_test() { SingleTon_2 test_singleTon_2_1 = SingleTon_2.getInstance(); SingleTon_2 test_singleTon_2_2 = SingleTon_2.getInstance(); System.out.println(test_singleTon_2_1.name); System.out.println("懒汉式对象一地址:" + test_singleTon_2_1); System.out.println("懒汉式对象二地址:" + test_singleTon_2_2); } }
|
多线程情况下的饿汉式和懒汉式
未加线程锁演示代码Thread_SingleTon.java
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| package CodeBase.SingleTon;
public class Thread_SingleTon { public static void main(String[] args) { System.out.println("=============多线程情况下============"); Thread_SingleTon_Test.Thread_Test_SingleTon_1(); Thread_SingleTon_Test.Thread_Test_SingleTon_2(); } }
class Thread_SingleTon_1 implements Runnable { @Override public void run() { try { Thread.sleep(20); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 饿汉式:"+ SingleTon_1.getInstance()); } }
class Thread_SingleTon_2 implements Runnable { @Override public void run() { try { Thread.sleep(20); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 懒汉式:" + SingleTon_2.getInstance()); } }
class Thread_SingleTon_Test { public static void Thread_Test_SingleTon_1() { Thread one = new Thread(new Thread_SingleTon_1()); Thread two = new Thread(new Thread_SingleTon_1()); one.start();; two.start(); } public static void Thread_Test_SingleTon_2() { Thread one = new Thread(new Thread_SingleTon_2()); Thread two = new Thread(new Thread_SingleTon_2()); one.start();; two.start(); } }
|
多线程中懒汉式的改进
- 为每个对象设置一个“互斥锁”,这表明,在每一个时刻只有一个线程持有该互斥锁,而其他线程若要获得该互斥锁,必须等到该线程(持有互斥锁的线程)将其释放。
- 为了使用这个“互斥锁”,在JAVA语言中提供了
synchronized
关键字,这个关键字即可修饰函数,也可以修饰代码,实际上可以将其理解为就是一个锁,当一个线程执行该临界代码的时候,用synchronized
给该线程先上锁,其它线程进不来,当线程代码执行完了的时候有释放该锁,只不过释放锁是隐式的不需要显示的指明,随代码的执行完毕,锁自动的被释放;
volatile
关键字可以禁止指令重排。
演示代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| package CodeBase.SingleTon;
public class Thread_SingleTon { public static void main(String[] args) { System.out.println("=============改进懒汉式=============="); Thread_SingleTon_Test.Test_Fix_Thread_SingleTon_2(); } }
class Fix_SingleTon_2 { String name; private volatile static Fix_SingleTon_2 fix_singleTon_2; private Fix_SingleTon_2(String name){ this.name = name; }; public static Fix_SingleTon_2 getInstance() { if(fix_singleTon_2 == null) { synchronized (Fix_SingleTon_2.class) { if(fix_singleTon_2 == null) { fix_singleTon_2 = new Fix_SingleTon_2("多线程懒汉式"); } } } return fix_singleTon_2; } }
class Fix_Thread_SingleTon_2 implements Runnable { @Override public void run() { try { Thread.sleep(20); } catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 改进懒汉式:" + Fix_SingleTon_2.getInstance()); } } class Thread_SingleTon_Test { public static void Test_Fix_Thread_SingleTon_2() { Thread one = new Thread(new Fix_Thread_SingleTon_2()); Thread two = new Thread(new Fix_Thread_SingleTon_2()); one.start();; two.start(); } }
|
六、final
关键字
使用场景
- 不希望类被继承时;
- 不希望父类的某个方法被子类覆盖、重写时;
- 不希望类中某个属性的值被修改时;
- 不希望某个局部变量被修改时。
注意事项及细节讨论
final
修饰的属性又叫常量,一般用XX_XX_XX
命名;
final
修饰的属性在定义是赋初值,并且不能再修改,赋值可以在下列位置:
- 定义时:如
public final double TAX_RATE=0.08
;
- 在构造器中;
- 在代码块中。
若final
修饰的属性时静态的,则初始化的位置只能在定义时和在静态代码块中,不能在构造器中赋值;
final
类不能继承,但可以实例化对象;
如类不是final
类,但类里有final
方法,则该方法不能被重写,但类可以被继承;
一般来说,若一个类已经是final
类,则不需要在此类中写final
方法;
final
不能修饰构造器;
final
往往和static
配合使用,效率更高,底层编译器做了优化:
如下代码:
- 直接访问
static final
定义的属性时,编译时就知道了a的值,所以直接访问不会初始化Final
类,即static
代码块不会被加载;
- 当
static final
定义的属性需要某个方法来获取时,编译时无法知道b的值,只有运行时才能知道,所以在访问b的值时,需要先初始化类,即static
代码块会被加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package demo;
public class test1 { public static void main(String[] args) { System.out.println("直接访问static final定义的属性:"+Final.a); System.out.println("======================="); System.out.println("访问static final定义的方法:"+Final.b); } }
class Final { static final int a = 10; static final int b = b();
static { System.out.println("Final类被加载。"); }
public static int b() { return 20; } }
|
- 包装类(
Integer,Double,Float,Boolean
)等都是final
类,String
也是final
类。
七、抽象类和抽象方法
基本概述
- 形式:
abstract class A{}
和public abstract void B()
;
- 当父类方法不确定时,考虑将该方法设计为抽象方法;
- 所谓抽象方法就是没有实现的方法,即没有方法体;
- 当一个类中存在抽象方法时,需要将该类声明为抽象方法;
- 一般来说,抽象类会被继承,由子类实现其抽象方法。
使用细节
- 抽象类不能被实例化;
- 抽象类不一定包含抽象方法。包含抽象方法的类必须为抽象类;
abstract
只能修饰方法和类;
- 抽象类可以有任意成员,如非抽象方法、构造器、静态属性等;
- 若一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为抽象类;
- 抽象方法不能用
private,final,static
修饰,因为这些关键字都是和重写相违背的。
八、接口
基本介绍
1 2 3 4 5 6 7 8 9 10
| interface 接口名{
}
class 类名 implements 接口{
}
|
- 在jdk7.0以前,接口中所有方法都没有方法体,都是抽象方法;jdk8.0后,接口中可以有静态方法(
static
)和默认方法(default
),即接口中可以有方法的具体实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| interface A{ void a(); public static void b() { System.out.println("静态方法。"); } default static void c() { System.out.println("默认方法。"); } }
|
使用细节
- 接口不能被实例化;
- 接口中所有方法都是
public
,接口中抽象方法可以不用使用abstract
;
- 一个普通类实现接口,必须将接口中所有方法实现;
- 抽象类实现接口时,可以不用实现接口抽象方法;
- 一个类可以同时实现多个接口,逗号间隔两个接口(
class A implements IB,IC
);
- 接口中的属性只能是
final
的,而且是public final static
修饰的,如int a = 1;
,实际上是public final static int a = 1;
即必须初始化;
- 接口中的属性访问形式:
接口名.属性
(静态属性访问);
- 一个接口不能继承其他的类,但能继承其他的接口:
interface A extend B,C{}
;
- 接口的修饰符只能是public和默认。
接口VS.继承
- 继承相当于父子关系,子类生来就有(自动拥有)父类的一些属性和方法;而接口相当于师徒关系,某个类若想拥有某个接口的方法,则必须去学习(实现接口中方法)。
- 如子类需要拓展功能,可以通过实现接口的方式,实现接口可以理解为对java单继承机制的一种补充。
- 解决问题不同
- 继承:主要提高代码的复用性和可拓展性;
- 接口:设计、设计好各种规范(方法),让其他人去实现这些方法;
- 接口比继承更加灵活
- 继承满足
is-a
的关系,而接口只需要满足like-a
的关系;
- 接口在一定程度上实现代码解耦(即:接口规范性+动态绑定),低耦合,高内聚。
接口多态特性
- 多态参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class test1 { public static void main(String[] args) { IF if01 = new Car(); if01 = new Bar(); A a = new B(); a = new C(); } } interface IF{} class Car implements IF{} class Bar implements IF{}
class A{} class B extends A{} class C extends A{}
|
- 多态数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
public class test1 { public static void main(String[] args) { Usb usb[] = new Usb[2]; usb[0] = new Phone(); usb[1] = new Camera();
for (int i = 0;i < usb.length;i++) { usb[i].work(); if (usb[i] instanceof Phone) { ((Phone) usb[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("相机工作中。"); } }
|
接口多态传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class test1 { public static void main(String[] args) { IG ig = new Teacher();
IH ih = new Teacher(); } }
interface IH{ void work(); } interface IG extends IH {} class Teacher implements IG{ public void work() { System.out.println("工作中。。。"); } }
|
九、内部类
类的五大成员:属性、方法、构造器、代码块、内部类。
内部类最大特点就是可以访问私有属性。
内部类的分类
定义在外部类局部位置上(如方法内)
定义在外部类的成员位置上
- 成员内部类(无
static
修饰)
- 静态内部类(有
static
修饰)
局部内部类
局部内部类定义在方法中或代码块中,其本质仍是一个类;
可以直接访问外部类的所有成员,包括私有的;
不能添加访问修饰符,因为它的地位是一个局部变量,局部变量不能访问修饰符的,但可以使用final
修饰,以保证其不被继承,因为局部变量也可以使用final
;
作用域:仅在定义它的方法或代码块中;
局部内部类访问局部内部类成员:直接访问;
外部类(内部类之外的第一个类)的方法中访问局部内部类成员:创建对象,再访问;
外部其他类(外部类之外的类)不能访问局部内部类;
若外部类和局部内部类的成员重名时,默认遵循就近一致原则,如果向访问外部类的成员,使用外部类.this.成员
去访问,如下代码演示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class test1 { public static void main(String[] args) { Outer outer = new Outer(); outer.m1(); System.out.println("outer对象的hashcode:"+outer); } }
class Outer { private int n1 = 100; public void m1() { final class Inner { private int n1 = 200; public void f1() { System.out.println("n1="+n1); System.out.println("n1="+Outer.this.n1); System.out.println("Outer.this对象的hashcode:"+Outer.this); } } Inner inner = new Inner(); inner.f1(); } }
|
匿名内部类
本质详解
有两个需求:
- 需求1:使用IA接口,实现接口内方法
传统方法:单独用类实现接口方法,并创建对象,如tiger1
- 需求2:Tiger类只使用一次,后面不再使用,单独定义一个类浪费资源
解决方法:使用匿名内部类简化,如tiger2
解决代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| public class test1 { public static void main(String[] args) { Outer outer = new Outer(); outer.method(); } }
class Outer { private int n1 = 100; public void method() { Tiger tiger1 = new Tiger(); tiger1.cry(); IA tiger2 = new IA() { @Override public void cry() { System.out.println("(匿名类)老虎叫。。。"); } };
tiger2.cry(); } } interface IA { void cry(); }
class Tiger implements IA { @Override public void cry() { System.out.println("(实现接口)老虎叫。。。"); } }
|
分析:
匿名内部类的使用
1 2 3 4 5 6 7
| class Outer$1 extend Father { @Override void speak() { System.out.println("匿名内部类重写 Father中speak方法"); } }
|
如下代码演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public class test1 { public static void main(String[] args) { Outer outer = new Outer(); outer.method(); } } class Outer { private int n1 = 100; public void method() { Father father = new Father("Jack"){ @Override void speak() { System.out.println("匿名内部类重写 Father中speak方法"); } }; System.out.println("father对象的运行类型:"+father.getClass()); father.speak(); Animal animal = new Animal() { @Override void eat() { System.out.println("动物吃东西。"); } }; animal.eat(); } } class Father { String name; public Father(String name) { this.name = name; System.out.println("接收到的名字:"+name); } void speak() {} } abstract class Animal { abstract void eat(); }
|
使用细节
- 调用可以动态绑定调用,也可以直接调用,内部类本身就会返回对象;
可以访问外部类的所有成员,包括私有成员;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| Animal animal = new Animal() { @Override void eat(String s) { System.out.println("动物吃"+s); } }; animal.eat("草");
new Animal() { @Override void eat(String s) { System.out.println("动物吃"+s); } }.eat("草");
|
应用场景
代码演示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
public class test1 { public static void main(String[] args) { Outer outer = new Outer(); outer.method(); } }
class Outer { private int n1 = 100; public void method() { CellPhone cellPhone = new CellPhone(); cellPhone.SmartClock(new Clock() { @Override public void ring() { System.out.println("起床了。"); } }); cellPhone.SmartClock(new Clock() { @Override public void ring() { System.out.println("上课了。"); } }); } }
interface Clock { void ring(); } class CellPhone { public void SmartClock(Clock clock) { clock.ring(); } }
|
成员内部类
定义在外部类的成员位置;
可以直接访问外部类的所有成员,包含私有的;
可以添加任意访问修饰符,因为它的地位就是类的一个成员;
作用域:和外部类的其他成员一样,作用域为整个外部类体;
成员内部类访问外部类:直接访问;
外部类访问内部类:创建对象再访问;
若外部类和局部内部类的成员重名时,默认遵循就近一致原则,如果向访问外部类的成员,使用外部类.this.成员
去访问;
外部其他类访问成员内部类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
public class test1 { public static void main(String[] args) { Outer outer = new Outer(); Outer.Inner inner1 = outer.new Inner(); inner1.say(); Outer.Inner inner2 = outer.getInner(); inner2.say(); } }
class Outer { private int n1 = 100; public class Inner { void say() { System.out.println("成员内部类输出。"); } } public Inner getInner() { return new Inner(); } }
|
静态内部类
定义在外部类的成员位置,并且有static
修饰;
可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员;
可以添加任意访问修饰符,因为它的地位就是类的一个成员;
作用域:和外部类的其他成员一样,作用域为整个外部类体;
静态内部类访问外部类:直接访问所有静态成员;
外部类访问静态内部类:创建对象再访问;
若外部类和局部内部类的成员重名时,默认遵循就近一致原则,如果向访问外部类的成员,使用外部类.成员
去访问;
外部其他类访问静态内部类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
public class test1 { public static void main(String[] args) { Outer outer = new Outer(); Outer.Inner inner1 = new Outer.Inner(); inner1.say(); Outer.Inner inner2 = outer.getInner(); inner2.say(); } }
class Outer { private int n1 = 100; static class Inner { void say() { System.out.println("静态内部类输出。"); } } public static Inner getInner() { return new Inner(); } }
|