10 封装
10.1 为什么要封装
封装是为了保护数据,这里的数据是指对象的属性(不是方法的局部变量)。
保护数据中的保护是什么意思?保护是指对数据的读写权限。
保护是如何实现的?或者说封装是如何实现的?是通过访问修饰符实现的。
10.2 访问修饰符
访问修饰符共有四个:
- public:公共的
- protected:受保护的
- 默认|default(什么都不写)
- private:私有的
10.2.1 public
public修饰的属性没有访问限制,是最宽松的封装。
例如:
public class Person {
public String name;//公有
}
name可以在任何地方被访问呢,也就是可以读和写
pubilc修饰类:类可以在任何地方被访问
public修饰属性和方法:属性和方法可以在任何地方被访问
10.2.2 private
private修饰的属性只能在类内被访问,类外无法访问,是最严格的封装。
例如:
public class Person {
private String name;//私有
}
name属性只能在Person类的类内使用,类外无法使用。
private不能修饰类
private class Classinfo{ //报错,private不能修饰类
}
public class Student {
}
再看一个例子:
public class Student {
int age;
private class Classinfo{ //不报错,因为Cassinfo类是Student类的成员
}
}
private可以修饰类的成员,无论类的成员是什么。
10.3 getter and setter
最典型封装是:共有方法封装私有属性
例如:
public class Person {
private String name; //私有的属性,类外不能访问
private int money; //私有的属性,类外不能访问
public String getName() { //共有的读方法,外界可以读取
return name;
}
public void setName(String name) { //共有的写方法,外界可以写入
this.name = name;
}
public int getMoney() { //共有的读方法,外界可以读取
return money;
}
public void setMoney(int money) { //共有的写方法,外界可以写入
if(money<0){ //封装中可以验证数据有效性
return;
}
this.money = money;
}
}
11 包
11.1 包和目录的关系
- 包:逻辑目录
当我们定义一个包时,会在硬盘上创建与包对应的目录。 - 目录:物理目录
目录是我们在硬盘上创建的,用于分离管理文件的东西。 - 逻辑目录和物理目录相同
逻辑目录和物理目录是一一对应的关系。
11.2 类和文件的关系
类定义在文件中
- 一个文件中只能有一个公有类,公有类的类名必须与文件名相同。
- 一个文件中可以有多个非共有类。
- 一个文件中的多个非公有类编译后每一个类是一个单独的class文件。
11.3 定义包
使用package定义包
package com.sunmer.teacher;
11.4 导入包
要使用某个包中的类,需要将类导入进来。
导入类:
import com.sunmer.oop.Person;//导入了Person类
import com.sunmer.oop.*;//导入了oop包下的所有类
特别强调:
包命名必须小写
11.5 default
默认访问修饰符是指什么都不写。
例如:
class Student {//Student类是默认访问修饰符
String name; //name是默认访问修饰符
Student(String name){ //构造函数是默认访问修饰符
}
void sayHello(){//sayHello方法是默认访问修饰符
}
}
默认访问修饰符修饰的类,属性,方法可以在同包中的类访问。
default可以修饰类,修饰属性,方法可以。
12 static
12.1 static的作用
在同一个类的对象中共享数据。
12.2 static与instance的区别
static:静态(属于类,只有一份)
instance:实例(实例也叫对象,就是new出来的堆的内存空间,实例是每个对象专有的,每new一次就分配一次内存)
- 实例变量是在new类时在堆中分配内存的。
- 构造函数可以为实例属性初始化。构造函数不会为静态属性初始化。
- 由于静态属性是所有对象共有的,所有对象不就是类吗,因此静态属性也称为类属性,或者类变量,或者类成员。
- 既然静态属性属于类,不属于某个具体的对象,因此在new对象时,不会给静态属性分配内存。那静态时什么时候分配内存呢?
- 当在程序运行期间,首次出现类名时,会暂时停止程序运行,去为类的静态属性分配内存,然后继续运行。
- 静态变量被分配在方法区中,常驻内存,永不回收。静态变量只有一份。相当于c语言的全局变量。
- 静态变量由类名操作,由类名赋值,取值。
- 类名调用静态属性时,可以省略类名。
12.2.1 static的加载过程。
- 首次出现类名时,为类的静态属性分配内存
12.2.2 instance的加载过程
- new的时候分配内存。
12.3 static变量
static可以修饰变量,称为静态变量。
12.4 static方法
static可以修饰方法,称为静态方法。
12.5 static块
还可以定义static块
类名首次出现时,先为静态变量分配内存,然后调用静态块,静态块可以为静态变量初始化。还可以干别的事情。
静态块只在类名首次出现时调用一次,以后再也不调用了。
例如:
public class Demo{
//构造为实例变量初始化
public Demo(int i){
this.i = i;
}
//静态块,为static变量初始化,静态块在首次类名出现时调用,但后于分配静态变量。
static{
age = 20;
}
static int age=0;
int i;
}
13 继承
13.1 为什么要继承
- 继承的出现提高了代码的复用性,提高软件开发效率。
- 继承的出现让类与类之间产生了关系,提供了多态的前提。
13.2 继承的定义格式
在程序中,如果想声明一个类继承另一个类,需要使用extends关键字。
class 子类 extends 父类 {
}
13.3 使用继承
代码编写步骤:
第一步:重构父类构造
- 首先为父类添加构造,父类的构造为父类中定义的属性初始化
- 子类定义的特有属性不是父类负责初始化的。
public Deparment(int ID, String name, int amount, String responsibility, String manager) {
super();
this.ID = ID;
this.name = name;
this.amount = amount;
this.responsibility = responsibility;
this.manager = manager;
}
第二步:重构子类构造
- 子类构造应该获得所有属性的初始值,本例中共6个属性,其中5个是父类的,1个是自己的
- 子类构造的第一行必须是调用父类构造,
- 如果不写super(),那么默认再子类第一行添加调用父类无参的构造super()
- 子类再调用父类构造代码super()的下面负责给自己的属性赋值
public PersonalDepartment(int ID, String name, int amount, String responsibility, String manager,int count) {
super(ID,name,amount,responsibility,manager);
this.count = count;
}
第三步:重构实例化子类对象
- 再测试类中new子类时,要为子类传入属性的参数
PersonalDepartment personalDepartment = new PersonalDepartment(110,"人事部",5,"负责招聘","卡卡",10);
第四步:测试
13.4 继承的注意事项
13.4.1 类只支持单继承,不允许多继承
class A{}
class B{}
class C extends A,B{} // C类不可以同时继承A类和B类
13.4.2 多个类可以继承一个父类
class A{}
class B extends A{}
class C extends A{} // 类B和类C都可以继承类A
13.4.3 允许多层继承
class A{}
class B extends A{} // 类B继承类A,类B是类A的子类
class C extends B{} // 类C继承类B,类C是类B的子类,同时也是类A的子类
13.5 继承的体系 之 Object类
Object是所有类的父类。
如果一个类没有显示定义父类,那么默认继承Object类。
Object类中没有定义属性,但是定义了12个方法,并且这些方法都是实例方法。因此每个对象都拥有这14个方法。
序号 | 方法签名 | 说明 |
---|---|---|
1 | String toString() | 返回当前对象本身的有关信息,返回字符串对象 |
2 | boolean equals(Object) | 比较两个对象是否是同一个对象,若是,返回true |
3 | Object clone() | 生成当前对象的一个副本,并返回 |
4 | int hashCode() | 返回该对象的哈希码值 |
5 | void finalize() | 在垃圾收集器将对象从内存中清除出之前做必要的清理工作。 |
6 | void wait() | 线程等待 |
7 | void wait(int) | 线程等待 |
8 | void wait(long,int) | 线程等待 |
9 | void notify() | 线程唤醒 |
10 | void notifyall() | 线程唤醒 |
11 | Class getClass() | 获取类结构信息,返回Class对象 |
13.6 继承后子类的成员的变化
了解了继承给我们带来的好处,提高了代码的复用性。继承让类与类或者说对象与对象之间产生了关系。那么,当继承出现后,类的成员之间产生了那些变化呢?
子类中的成员包括:
- 子类自己定义的属性和方法
- 从父类继承的属性和方法
- 子类不能使用从父类继承的私有成员
13.7 继承中的this(调用子类成员)
- this可以调用子类成员。
- this可以调用子类重载的构造函数。
13.8 继承中的super(调用父类成员)
- super调用父类成员。
- super调用父类构造函数,必须是子类构造函数的第一行。
13.9 方法重写(方法覆盖|override)
方法重写的注意事项:
- 子类方法覆盖父类方法,必须要保证权限大于等于父类权限。
class Fu(){
void show(){}
public void method(){}
}
class Zi() extends Fu{
public void show(){} //扩大show的访问权限,编译运行没问题
void method(){} //缩小method的访问权限,编译错误
}
- 方法的返回值类型 方法名 参数列表都要一样。
14 接口
14.1 接口与定义规范
与定义类的class不同,接口定义时需要使用interface关键字。
接口定义语法格式:
public interface 接口名 {
抽象方法1;
抽象方法2;
抽象方法3;
}
定义接口
public interface IShout {
}
定义接口就是定义规范,接口中的抽象方法就是具体的规范。
14.2 实现类的接口
接口实现语法格式
class 类 implements 接口 {
重写接口中方法
}
在类实现接口后,该类就会将接口中的抽象方法继承过来,此时该类需要重写该抽象方法,完成具体的逻辑。
14.3 接口的多实现
interface Fu1
{
void show1();
}
interface Fu2
{
void show2();
}
class Zi implements Fu1,Fu2// 多实现。同时实现多个接口。
{
public void show1(){}
public void show2(){}
}
14.4 类继承类同时实现接口
class Fu {
public void show(){}
}
interface Inter {
pulbic abstract void show1();
}
class Zi extends Fu implements Inter {
public void show1() {
}
}
接口的出现避免了单继承的局限性。父类中定义的事物的基本功能。接口中定义的事物的扩展功能。
14.5 接口的规定
- 接口中可以定义变量,但是变量必须有固定的修饰符修饰,public static final 所以接口中的变量也称之为常量,其值不能改变。
- 接口中可以定义方法,方法也有固定的修饰符,public abstract
- 接口不可以new,不可以实例化。
- 子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化。否则子类是一个抽象类。
- 接口不能继承类
- 接口可以继承接口
- 接口没有最高层,类的最高层是Object
- 接口中不允许有普通方法,所以接口中的方法都是抽象(除了default方法和static方法)
- 接口不是类,所以没有构造
14.6 接口的继承
interface Fu1{
void show();
}
interface Fu2{
void show1();
}
interface Fu3{
void show2();
}
interface Zi extends Fu1,Fu2,Fu3{
void show3();
}
15 多态
15.1 什么是多态
多态就是多种形态,多种形式。
多态的实现方法有两种
- 方法重写(公认的多态)
- 方法重载(非公认的多态)
15.2 多态的语法格式
多态的定义格式:就是父类的引用变量指向子类对象
父类类型 变量名 = new 子类类型();
变量名.方法名();
Pet pet = new Cat();
pet.toString();
- 普通类多态定义的格式
父类 变量名 = new 子类();
例如:
class Fu {}
class Zi extends Fu {}
Fu f = new Zi();//类的多态使用
- 抽象类多态定义的格式
抽象类 变量名 = new 抽象类子类();
例如:
abstract class Fu {
public abstract void method();
}
class Zi extends Fu {
public void method(){
System.out.println(“重写父类抽象方法”);
}
}
Fu fu= new Zi();//类的多态使用
- 接口多态定义的格式
接口 变量名 = new 接口实现类();
例如:
interface Fu {
public abstract void method();
}
class Zi implements Fu {
public void method(){
System.out.println(“重写接口抽象方法”);
}
}
Fu fu = new Zi();//接口的多态使用
-
注意事项:
同一个父类的方法会被不同的子类重写。在调用方法时,调用的为各个子类重写后的方法。
Person p1 = new Student();
Person p2 = new Teacher();
p1.work(); //p1会调用Student类中重写的work方法
p2.work(); //p2会调用Teacher类中重写的work方法
当变量名指向不同的子类对象时,由于每个子类重写父类方法的内容不同,所以会调用不同的方法。
15.3 多态的转型
多态的转型分为向上转型与向下转型两种:
- 向上转型就是父类引用指向子类对象
- 向下转型就是子类引用指向父类对象
15.3.1 向上转型
向上转型:当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。
使用格式:
父类类型 变量名 = new 子类类型();
例如:
Person p = new Student();
15.3.2 向下转型
向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。如果是直接创建父类对象,是无法向下转型的!
使用格式:
子类类型 变量名 = (子类类型) 父类类型的变量;
例如:
Student stu = (Student) p; //变量p 实际上指向Student对象
15.4 多态的好处和弊端
当父类的引用指向子类对象时,就发生了向上转型,即把子类类型对象转成了父类类型。向上转型的好处是隐藏了子类类型,提高了代码的扩展性。
但向上转型也有弊端,只能使用父类共性的内容,而无法使用子类特有功能,功能有限制。看如下代码:
//描述动物类,并抽取共性eat方法
abstract class Animal {
abstract void eat();
}
// 描述狗类,继承动物类,重写eat方法,增加lookHome方法
class Dog extends Animal {
void eat() {
System.out.println("啃骨头");
}
void lookHome() {
System.out.println("看家");
}
}
// 描述猫类,继承动物类,重写eat方法,增加catchMouse方法
class Cat extends Animal {
void eat() {
System.out.println("吃鱼");
}
void catchMouse() {
System.out.println("抓老鼠");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Dog(); //多态形式,创建一个狗对象
a.eat(); // 调用对象中的方法,会执行狗类中的eat方法
// a.lookHome();//使用Dog类特有的方法,需要向下转型,不能直接使用
// 为了使用狗类的lookHome方法,需要向下转型
// 向下转型过程中,可能会发生类型转换的错误,即ClassCastException异常
// 那么,在转之前需要做健壮性判断
if( !a instanceof Dog){ // 判断当前对象是否是Dog类型
System.out.println("类型不匹配,不能转换");
return;
}
Dog d = (Dog) a; //向下转型
d.lookHome(); //调用狗类的lookHome方法
}
}
16 面向接口编程
- 接口作为子类实现
- 接口作为方法参数
- 接口作为方法返回值
- 接口作为类属性
16.1 接口作为子类的实现
interface A{
void method();//1:定义规范
}
class B implements A{
public void method(){//2:实现规范
}
}
接口作为子类实现使用时,接口用于定义规范,子类用于实现规范。
16.2 接口作为方法参数
interface A{
void sound();
}
class B implements A{
public void sound(){
System.out.println("b sound is running")
}
}
class C implements A{
public void sound(){
System.out.println("c sound is running")
}
}
class Test {
//参数a是接口类型
public static void call(A a){//可以传入任何实现了A接口的子类实例,表现出多态的特性
a.sound();
}
public static void main(String args[]){
A a = new B();
Test.call(a);
a = new C();
Test.call(a);
}
}
- 接口作为方法参数时,可以传入任何实现了该接口的子类实例,接口对象调用方法时,调用的是传入子类的方法。
- 这种设计的原理是里氏替换原则,即父类引用指向子类实例。
16.3 接口作为方法返回值
interface A{
void sound();
}
class B implements A{
public void sound(){
System.out.println("b sound is running")
}
}
class C implements A{
public void sound(){
System.out.println("c sound is running")
}
}
class Test {
public static A getInstance(String type){//方法返回值是A接口,那么可以返回任何实现该接口的子类实例
A a = null;//定义接口对象
if(type==null){
throw new RuntimeException("非法参数异常")
}
if(type.equalsIgnoreCase("b")){
return new B();
}else if(type.equalsIgnoreCase("c")){
return new C();
}else{
return null;
}
}
public static void main(String args[]){
A a = Test.getInstance("b");
a = Test.getInstance("c");
}
}
- 接口作为方法返回值时,可以返回任何实现了该接口的子类实例,接口对象调用方法时,调用的是传入子类的方法。
- 这种设计的原理是里氏替换原则,即父类引用指向子类实例。
16.4 接口作为属性
interface A{
void sound();
}
class B implements A{
public void sound(){
System.out.println("b sound is running")
}
}
class C implements A{
public void sound(){
System.out.println("c sound is running")
}
}
class D{
private A a = null;//接口A作为类D的属性,可以传入任何实现A接口的子类对象
public void setA(A a){
this.a = a;
}
public A getA(){
return this.a;
}
}
class Test {
public static void main(String args[]){
D d = new D();
A a = new B();//创建了A接口的子类B对象
d.setA(a);//A接口的子类B对象 传入给 D类的属性a;
d.getA().sound();//调用的是B的sound()
a = new C();//创建了A接口的子类C对象
d.setA(a);//A接口的子类C对象 传入给 D类 的属性a;
d.getA().sound();//调用的是C的sound()
}
}
- 接口作为类的属性时,可以传入任何实现了该接口的子类实例,接口对象调用方法时,调用的是传入子类的方法。
- 这种设计的原理是里氏替换原则,即父类引用指向子类实例。
面向接口编程的核心是:父类引用指向子类对象,换个角度来理解就是凡是使用父类的地方都可以使用子类替换父类。
17 内部类和匿名类
17.1 内部类
介绍:
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。
当一个类只在另外一个类的内部使用,不会在其他类中使用时,应该定义为内部类。
提示:类的成员包括(属性、方法、代码块、内部类)。
语法:
class Outer{ //外部类
class Inner{ //内部类
}
}
分类
- 实例内部类
- 静态内部类
代码
实例内部类示例:
class Outer{
class Inner{ //实例内部类,没有用static修饰的内部类
}
}
实例内部类在实例外部类对象时分配内存,要想使用实例内部类对象,就需要先实例化外部类对象。
例如:
class Outer{
class Inner{ //实例内部类,没有用static修饰的内部类
public void innerClassMethod(){
System.out.println("innerClassMethod");
}
}
}
public class InnerClassDemo {
public static void main(String[] args) {
//分开写的写法
Outer outer = new Outer();
Outer.Inner inner1 = outer.new Inner();
inner1.innerClassMethod();
//合并的写法
Outer.Inner inner2 = new Outer().new Inner();
inner2.innerClassMethod();
}
}
静态内部类示例:
class Outer{
static class Inner{ //实例内部类,没有用static修饰的内部类
}
}
静态内部类是在外部类首次出现时分配内存的,使用外部类就可以直接创建出内部类的对象,不需要实例化外部类。
例如:
class Outer{
static class Inner{ //静态内部类,使用static修饰的内部类
public void innerClassMethod(){
System.out.println("innerClassMethod");
}
}
}
public class InnerClassDemo {
public static void main(String[] args) {
//创建静态内部类的实例
Outer.Inner inner = new Outer.Inner();
inner.innerClassMethod();
}
}
17.2 匿名类
介绍:
- 匿名内部类又叫匿名类,匿名类没有类名,我们可以在实例化时声明一个匿名类。
创建方式
- 匿名类使用接口或抽象类创建
场景
- 匿名类仅适用于只会使用一次的场景,匿名类不可复用。
语法
接口或抽象类 对象名 = new 接口或抽象类(){
//实现接口或抽象类中的所有方法
@override
public void 方法名(){
}
}
代码
interface Programmer {
void develop();
}
public class TestAnonymousClass {
public static void main(String[] args) {
Programmer anotherProgrammer = new Programmer() {
@Override
public void develop() {
System.out.println("我是在方法中实现了接口的匿名内部类");
}
};
anotherProgrammer.develop();
}
}
运行结果:
我是在方法中实现了接口的匿名内部类
匿名类也可以使用抽象类,把接口
interface Programmer {
void develop();
}
改为抽象类
abstract class Programmer {
abstract void develop();
}
也可以