封装
封装其实就是针对类属性的私有化(封装),即属性私有。同时向外部提供公有的set和get方法。
public class Cat {
private String name;
private int age;
public void sleep() {
System.out.println(this.name + "爱睡觉");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
System.out.println(cat.getName());
cat.setName("年年");
System.out.println(cat.getName());
cat.sleep();
}
}
输出结果
null
年年
年年爱睡觉
封装的作用
-
提高程序的安全性,保护数据
-
隐藏代码的实现细节
-
统一用户的调用接口
-
提高系统的可维护性
良好的封装,便于修改内部代码, 可进行数据完整性检测,提高可维护性和保证数据的有效性。
继承
继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。
为什么需要继承?继承的作用?
- 继承的本质在于抽象。类是对对象的抽象,继承是对某一批类的抽象。
- 为了提高代码的复用性。
继承是类和类之间的一种关系。子类继承父类,使用关键字extends来表示。extends的意思是“扩展”。
public class Cat extends Animal{}
子类和父类具有“is a“的关系,上例中Cat is a Animal(猫是动物)。java中类只有单继承,子类只能继承一个父类,但父类可以有多个子类。但是接口之间可以多继承。
父类中的属性和方法可以被子类继承。
每一个类默认继承了Object类。
super关键字
上面我们知道了this代表当前对象,而super则代表父类对象。可以使用super调用父类的属性或方法。
public class Animal {
private String name;
private int age = 3;
public void eat() {
System.out.println("动物吃东西");
}
public Animal() {
System.out.println("父类无参初始化");
}
public Animal(String name) {
this.name = name;
System.out.println("父类有参初始化");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Cat extends Animal {
private int sex;
public Cat() {
////默认在第一行使用super()调用父类的无参构造方法
System.out.println("子类无参初始化");
}
public Cat(String name) {
//也可以通过super(参数列表);显示调用父类有参构造器
super(name);
System.out.println("子类有参初始化");
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
System.out.println(cat.getAge());
cat.setAge(2);
System.out.println(cat.getAge());
cat.eat();
System.out.println("=============");
Cat cat1 = new Cat("小猫");
System.out.println(cat1.getName());
}
}
输出结果
父类无参初始化
子类无参初始化
3
2
动物吃东西
=============
父类有参初始化
子类有参初始化
小猫
super注意点
- 用super调用父类构造方法,必须是构造方法中的第一个语句。
- super只能出现在子类的方法或者构造方法中。
- super 和 this 不能够同时调用构造方法。(因为this也是在构造方法的第一个语句)
方法重写
方法重写只存在于子类和父类(包括直接父类和间接父类)之间。在同一个类中方法只能被重载,不能被重写。
静态方法可以被继承,但不能重写
- 父类的静态方法不能被子类重写为非静态方法 //编译出错
- 父类的非静态方法不能被子类重写为静态方法;//编译出错
- 子类可以定义与父类的静态方法同名的静态方法(但是这个不是覆盖),但是调用时只和变量声明的类型相关。
A类继承B类 A和B中都一个相同的静态方法test
B a = new A();
a.test();//调用到的是B类中的静态方法test
A a = new A();
a.test();//调用到的是A类中的静态方法test
私有方法无法访问,所以无法重写。
重写的语法
- 方法名必须相同
- 参数列表必须相同
- 访问控制修饰符可以被扩大,但是不能被缩小: public protected default private
- 抛出异常类型的范围可以被缩小,但是不能被扩大 ClassNotFoundException ---> Exception
- 返回类型可以相同,也可以不同,如果不同的话,子类重写后的方法返回类型必须是父类方法返回类型的子类型
方法重写是为了满足子类特定功能需要的扩展。
多态
多态概念
- 多态是方法或对象具有多种形态,是面向对象的第三大特征。
- 多态的前提是两个对象(类)存在继承关系,多态是建立在封装和继承基础之上的。
具体实现
对象的多态是多态的核心和重点。
规则:
- 一个对象的编译类型与运行类型可以不一致
- 编译类型在定义对象时,就确定了,不能改变,而运行类型是可以变化的
- 编译类型看定义对象时 = 号的左边,运行类型看 = 号的右边
注意事项:
- 多态是方法的多态,属性没有多态性。
- 编写程序时,如果想调用运行时类型的方法,只能进行类型转换。不然通不过编译器的检查。但是如果两个没有关联的类进行强制转换,会报:ClassCastException。
- 多态的存在要有3个必要条件:要有继承,要有方法重写,父类引用指向子类对象
存在条件:
- 有继承关系
- 子类重写父类方法
- 父类引用指向子类对象,即向上转型。
注:java中的方法调用,是运行时动态和对象绑定的,不到运行的时候,是不知道到底哪个方法被调用的。
注:多态可以通过方法重写、接口、抽象类和抽象方法等方式实现。
向上转型
本质:父类的引用指向子类的对象(自动转型)
特点:
- 编译类型看左边,运行类型看右边
- 可以调用父类的所有成员(需遵守访问权限)
- 不能调用子类的特有成员
- 运行效果看子类的具体实现
语法:
父类类型 引用名 = new 子类类型();
//右侧创建一个子类对象,把它当作父类看待使用
代码示例
public class Animal {
public void sleep(){
System.out.println("动物睡觉");
}
}
public class Cat extends Animal {
@Override
public void sleep() {
System.out.println("猫睡觉");
}
}
public class Dog extends Animal {
@Override
public void sleep() {
System.out.println("狗睡觉");
}
}
public class Test {
public static void main(String[] args) {
Animal a1 = new Cat();
a1.sleep();
Animal a2 = new Dog();
a2.sleep();
}
}
输出结果
猫睡觉
狗睡觉
向下转型
本质:一个已经向上转型的子类对象,将父类引用转为子类引用(强制转型)
特点:
- 只能强制转换父类的引用,不能强制转换父类的对象
- 要求父类的引用必须指向的是当前目标类型的对象
- 当向下转型后,可以调用子类类型中所有的成员
语法:
子类类型 引用名 = (子类类型) 父类引用;
//用强制类型转换的格式,将父类引用类型转为子类引用类型
注:使用强制类型转换调用方法时,可能出现ClassCastException 类型转换异常。
public class Animal {
public void sleep(){
System.out.println("动物睡觉");
}
}
public class Cat extends Animal {
@Override
public void sleep() {
System.out.println("猫睡觉");
}
public void aaa(){
System.out.println("猫抓老鼠");
}
}
public class Dog extends Animal{
@Override
public void sleep() {
System.out.println("狗睡觉");
}
public void bbb(){
System.out.println("狗看家");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Cat();
a.sleep();
Dog d = (Dog) a;
d.sleep();
}
}
输出结果
猫睡觉
Exception in thread "main" java.lang.ClassCastException: org.example.test.Cat cannot be cast to org.example.test.Dog
at org.example.test.Test.main(Test.java:8)
为了避免类型转换异常的问题,我们引出 instanceof 比较操作符,用于判断对象的类型是否为XX类型或XX类型的子类型。
- 格式:对象 instanceof 类名称
- 解释:这将会得到一个boolean值结果,也就是判断前面的对象能不能当作后面类型的实例
public class Test {
public static void main(String[] args) {
Animal a = new Cat();
a.sleep();
if (a instanceof Dog) {
Dog d = (Dog) a;
d.sleep();
} else if (a instanceof Cat) {
Cat c = (Cat) a;
c.aaa();
}
}
}
输出结果
猫睡觉
猫抓老鼠
方法绑定
执行调用方法时,系统根据相关信息,能够执行内存地址中代表该方法的代码。分为静态绑定和动态绑定。
静态绑定:在编译期完成,可以提高代码执行速度。
动态绑定:通过对象调用的方法,采用动态绑定机制。这虽然让我们编程灵活,但是降低了代码的执行速度。这也是JAVA比C/C++速度慢的主要因素之一。JAVA中除了final类、final方、static方法,所有方法都是JVM在运行期才进行动态绑定的。
多态:如果编译时类型和运行时类型不一致,就会造成多态。
内部类
内部类就是在一个类的内部在定义一个类,比如,A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了。
外部类访问成员内部类访问方式:创建对象,再访问。
内部类分为四种:
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
成员内部类
成员内部类是定义在外部类的成员位置上的内部类。
成员内部类的使用
1.可以直接访问外部类的所有成员,包括私有的。
2.成员内部类中不能写静态属性和方法。
3.作用域和外部类的其他成员一样,为整个类体。
4.如果外部类和内部类的成员重名时,内部类访问的话,遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
5.外部其他类访问成员内部类有三种方式:第一种实例化外部类,然后访问;第二种将内部类作为外部类的成员实例化然后访问;第三种在外部类里面编写一个方法,可以返回内部类对象。(其实也可以理解为一种,最终都是通过实例化内部类再访问)
public class Test {
public static void main(String[] args) {
//第一种方式 方法调用
Outer outer = new Outer();
outer.sayInnerHello();
//第二种方式 实例化内部类调用
Outer.Inner inner = outer.new Inner();
inner.sayHello();
//第三种方式 返回内部类对象 再进行调用
inner = outer.getInner();
inner.sayHello();
}
}
//外部类
class Outer {
private String name = "外部类";
public void sayHello() {
System.out.println("outer:name=" + name);
}
public void sayInnerHello() {
Inner inner = new Inner();
inner.sayHello();
}
//内部类
public class Inner {
private String name = "内部类";
public void sayHello() {
// Outer.this.sayHello();
System.out.println("inner:name=" + name);
System.out.println("outer:name=" + Outer.this.name);
System.out.println("======");
}
}
//返回内部类
public Inner getInner() {
return new Inner();
}
}
输出结果
inner:name=内部类
outer:name=外部类
======
inner:name=内部类
outer:name=外部类
======
inner:name=内部类
outer:name=外部类
======
静态内部类
静态内部类是定义在外部类的成员位置上,并且有static修饰。静态内部类能够直接被外部类给实例化,不需要使用外部类对象。
Outer.Inner inner = new Outer.Inner();
特点:
- 可以直接访问外部内的所有静态成员,包含私有的,但不能直接访问非静态的成员
- 成员内部类中可以写静态属性和方法,也可以写非静态的。
- 作用域和外部类的其他成员一样,为整个类体
- 如果外部类和内部类的成员重名时,内部类访问的话,遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
局部内部类
- 局部内部类可以直接访问外部类的所有成员,包含私有的。
- 局部内部类不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用访问修饰符的,但是可以使用final修饰。
- 作用域仅仅在定义它的方法或代码块中。
- 外部类访问局部内部类,访问方式:创建对象,再访问(注意:必须在作用域内)
- 外部其他类不能直接访问局部内部类(因为局部内部类是一个局部变量)
- 如果外部类和局部内部类的成员重名时,默认遵循就近访问原则,如果打破原则访问外部类成员,则可以使用(外部类名.this.成员)去访问。
public class Outer {
private int y = 3;
private void sayHello(){
System.out.println("hi");
}
public void test() {
int y = 5;
class Inner {
private void printOuter(){
System.out.println(Outer.this.y);
}
private void print(){
System.out.println(y);
}
private void hi(){
sayHello();
}
}
Inner inner = new Inner();
inner.print();
inner.hi();
inner.printOuter();
}
}
public class Test {
public static void main(String[] args) {
Outer outer = new Outer();
outer.test();
}
}
输出结果
5
hi
3
匿名内部类
【注】这是工作中最常用的一种内部类。
匿名对象
什么是匿名对象?简单来说,如果一个对象只需要使用一次,那么我们就是需要new Object().method() 就可以,而不需要生成变量引用,这就是匿名对象。匿名内部类跟匿名对象是一个道理。
匿名内部类
1. 匿名内部类需要依托于其他类或者接口来创建,如果依托的是类,那么创建出来的匿名内部类就默认是这个类的子类。如果依托的是接口,那么创建出来的匿名内部类就默认是这个接口的实现类。
2. 匿名内部类的声明必须是在使用new关键字的时候,创建时一般需要重写方法,然后调用。
3. 因为没有名字,匿名内部类没有构造器,也无法被继承。
4. 匿名内部类可以减少代码冗余。
new 外部类().方法(new 接口|抽象类|父类(){
接口方法的实现();
或
重写抽象类或父类的方法() ;
}).method();
接口名称 obj = new 接口名称() {
重写抽象方法;
}
obj.method();
示例
public interface School {
void info();
void testScore();
}
public class Student {
private String name;
private static int age = 18;
private int score;
public void hi() {
System.out.println("打招呼");
}
public static void go() {
System.out.println("上学");
}
public Student() {
}
public Student(String name, int score) {
this.name = name;
this.score = score;
}
//方式一 在new中重写方法,通过变量名调用
public void test() {
int score = 90;//重名属性 默认就近原则
//new接口时重写方法并声明变量
School s = new School() {
@Override
public void info() {
System.out.println(Student.this.name);
System.out.println(Student.age);
System.out.println(Student.this.score);
Student.go();
Student.this.hi();
}
@Override
public void testScore() {
System.out.println("考试得分:" + score);
}
};
//调用方法
s.info();
s.testScore();
}
//链式调用 但只能调用一个方法
public void test2() {
int score = 60;
//直接重写并调用
new School() {
@Override
public void info() {
}
@Override
public void testScore() {
System.out.println("考试得分:" + score);
}
}.testScore();
}
}
public class Test {
public static void main(String[] args) {
new Student("小明",80).test();
System.out.println("=========");
new Student().test2();
}
}
输出结果
小明
18
80
上学
打招呼
考试得分:90
=========
考试得分:60