一、封装
将对象的属性和行为封装到一个方法里面
1.目的: 为了不直接暴露给外界,不让让外界直接访问,如果外界直接对属性的值进行操作可能不是我们想要的值,可能不符合逻辑此时我们就需要将属性封装起来不让 外界直接访问。
2. 广义上 定义方法是封装,定义类也是封装
3. 狭义上是 类里的属性和行为的封装
把类的属性私有化,不让外界直接访问。
可以提供getter setter方法,让外界访问,在这些方法中设置一些操作防止出现不是我们想要的值
public class Person3 {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age <0 || age > 100){
System.out.println("Invalid Age");
System.out.println("again input");
}else{
this.age = age;
}
}
public static void main(String[] args) {
Person3 p = new Person3();
p.setAge(-1);
}
}
结果为
二、继承
1.目的
为了减少重复代码
对象可能具有相同的属性和行为,每个人有姓名,年龄,性别等属性;吃饭睡觉的行为。可以将这些共同的属性放到一个类(父类、基类、超类)里,在定义一个类(子类)去继承它。该类可以由自己的属性和行为;比如小明特有的行为是会弹钢琴,
2.特点
1.多个子类可以继承同一个父类
2. 不能继承父类的构造器
3.子类的构造器可以使用super(有参传参),子类会默认的调用父类的无参构造器,如果父类中没有无参构造器(可以是系统默认的),必须显示的调用super(有参传参)。且super(有参传参) 必须放在首行。
父类
public class Aperson {
private String name;
private char gender;
private int age;
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Aperson(String name, int age,char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public Aperson(){
}
}
子类:
public class Student extends Aperson{
private String studentId;
private String classNo;
//添加自己的构造器
public Student(String studentId, String classNo){
super("小黑",10,'男');
this.studentId = studentId;
this.classNo = classNo;
}
public String getStudentId() {
return studentId;
}
public void setStudentId(String studentId) {
this.studentId = studentId;
}
public String getClassNo() {
return classNo;
}
public void setClassNo(String classNo) {
this.classNo = classNo;
}
}
4.子类的构造器必须至少有一个调用了父类的构造器,因为子类需要初始化
5.支持多重(层)继承
public class A {
private String name;
private int age;
}
class B extends A{}
class C extends B{}
6. 如果父类的方法不能满足子类的需求,子类可以重写父类的方法,
方法名和形参列表和父类一样
返回值类型可以和父类型一样也可以是父类的子类型
访问修饰符可以比父类大也可以一样
7.重写与重载
重写
发生在父类与子类之间,子类重写父类的方法
方法名和形参列表都要相同
返回类型:重写方法返回类型可以是被重写方法返回类型的子类
修饰符: 重写方法的访问修饰符一定要大于被重写方法的访问修饰符
异常:重写方法的异常一定不能抛出新的检查异常 或者声明比被重写方法更加宽泛的检查性异常
重载
发生在一个类中的
方法名相同 参数列表不同(类型,个数、顺序)
返回类型:无要求,可以相同也可以不同
三、多态
一个对象的多种形态
分为两种:向上转型和向下转型
package com.oop.day02.polymorphism;
public class Animal {
private String color;
private String name;
private int age;
public Animal() {
}
public Animal(String color, String name, int age) {
this.color = color;
this.name = name;
this.age = age;
}
//动物都会有发出声音的行为
public void noise(){
System.out.println("动物都会叫");
}
}
class Dog extends Animal{
public void noise(){
System.out.println("---汪汪汪---");
}
public void lookHouse(){
System.out.println("---狗狗会看家---");
}
public Dog(String color, String name, int age) {
super(color, name, age);
}
}
class Cat extends Animal{
public void noise(){
System.out.println("---喵喵喵---");
}
public void getMouse(){
System.out.println("---猫猫会抓老鼠---");
}
public Cat(String color, String name, int age) {
super(color, name, age);
}
}
1. 向上转型
父类型的变量指向子类型的对象。是隐式转换
父类 名字 = new 子类();
向上转型后的对象,只能访问父类中的成员(编译时看变量(左边))
调用的是重写过的方法那么调用的一定是重写方法(运行时看对象(右边))
目的: 形式参数是父类型的变量,可以传子类型的对象。更加灵活,减少代码。
测试类
public class AnimalTest {
public static void main(String[] args) {
Animal a = new Cat("粉色","凯蒂",4);
a.noise();//编译期间没问题 因为类型里面有该方法 运行期间执行的是对象的类型里的方法逻辑
//a.getMouse(); 调用不到该方法,因为a 这个变量的类型里没有该方法(编译期间看变量类型)
test(a);
Dog dog = new Dog("黄色","狗狗狗",13);
test(dog);
}
//测试 : 执行动物的叫声 这就是向上造型的优势所在,父类型的变量作为参数可以传入不同的子类型对象
public static void test(Animal animal){
animal.noise();
}
}
2. 向下转型
父类型的变量赋值给子类型的变量,需要强制转换,是显示转换
为了避免ClassCastExceprion异常
用instanceOf来判断变量指向的对象是否为同一类型。
子类型 变量名 = (子类型) 父类型变量
测试类
public class AnimalTest2 {
public static void main(String[] args) {
//创建一个父类型的变量引用子类型的变量
Animal a = new Dog("黄色","hah",5);
a.noise();
//a.LookHouse(); 编译期间看变量类型 没有该方法 所以报错
Dog d = (Dog)a;
d.lookHouse();
//上述代码没有问题 但是在真正编程时有可能写成如下代码
//编译期间,不报错。但是运行期间就会报异常
// Cat c = (Cat)a;
// c.getMouse();
//应该避免上述情况发生 只需要instanceof即可
if(a instanceof Cat){//因为a 指向的是 一个dog对象 不属于cat类型 因此进不去所以避免报错
Cat c = (Cat)a;
c.getMouse();
}
}
}
注意:
1. 强转时候编译器会检查,是否有父子关系。没有父子关系的强制直接报编译错误
eg:
class Foo {
public int a;
public Foo() {
a = 3;
}
public void addFive() {
a += 5;
}
}
class Bar extends Foo {
public int a;
public Bar() {
a = 8;
}
public void addFive() {
this.a += 5;
}
}
public class SameName {
public static void main(String[] args) {
Foo foo = new Bar();
foo.addFive();
System.out.println("Value: " + foo.a);//调的是左边的
}
}
运行结果:
解析:
属性看编译类型,方法看运行类型(前提是重写了父类的方法)
名字相同,a是父类里的3(属性看变量类型),调用的是子类的addFive()(方法看右边的运行类型),所以子类的a变成了8+5=13,父类的a没变还是 3.
下图为它的JVM内存结构
四、抽象类
关键字:abstract
当父类中的某个方法的行为不能满足子类的需求时,子类需要重写父类中的方法,所以永远都执行不到父类的方法体,因此我们可以将该方法定义为抽象方法,即没有方法体,也就是没有{}。但是必须在方法名()后加“ ;”。
eg: public void add();
1. 被abstract修饰的类是抽象类,被abstract修饰的方法是抽象方法
2. 含有抽象方法的类必须被abstract修饰
3. 抽象类里可以有抽象方法,也可以有普通方法。
4. 父类为抽象类,子类必须重写父类中的所有抽象方法,或者子类也定义为抽象类。
5. 抽象类里可以有抽象方法和普通方法
6. 抽象类不能被final修饰,因为抽象类最大的意义就是被继承。
五、接口类
1.简介
java中不支持多继承,但是有时候我们需要从几个不相关的类中抽象出一个子类去继承它们的方法
这时就可以用接口来实现达到多继承的效果。
2.特点
(1)定义接口关键字:interface
(2)可以理解为是一种规范
(3)接口里的属性默认被 public static final 修饰
(4)接口里的方法默认被 public abstract修饰
(5)接口不能存在构造器,不能实例化,没有任何意义。
因为接口里的属性被static修饰,所以只能初始化一次,有构造器也没有意义。
(7)接口可以实现多继承
3.接口的实现
实现接口的关键字:implement
实现接口必须重写接口里的所有抽象方法,如果没有实现全部的方法,那么这个类必须被abstract修饰即声明为抽象类。
4.接口的继承
接口可以实现多继承
一个子类可以实现多个接口,从而达到多继承的效果
public interface InterA {
void showInfo();
}
interface InterB {
void showInfo();
int calculate(int a,int b);
}
/**
* 第一种情况: 一个子类可以实现多个接口,从而达到多继承的效果
*
*/
class ClassA implements InterA ,InterB{
@Override
public void showInfo() {
System.out.println("ClassA");
}
@Override
public int calculate(int a, int b) {
return a+b;
}
}
public class InterATest {
public static void main(String[] args) {
//因为classA是InterA和InterB的子类型,因此可以向上造型
InterA x = new ClassA();
x.showInfo();
//此时想要使用x指向的对象,调用里面的计算功能
//针对于这道题来说可以向下转型成ClassA,也可以强制转换成InterB
if(x instanceof ClassA){
ClassA y = (ClassA)x;//向下转型 x此时是InterA类型
int result = y.calculate(3,5);
System.out.println(result);
}
//第二种方式
if(x instanceof ClassA){
InterB b = (InterB)x;//向下转型 因为ClassA是InterB的子类所以可以向下转型
int result = b.calculate(3,5);
System.out.println(result);
}
}
}
子接口继承了父接口中的所有抽象方法
5. 接口在1.8之后的新特性
不同于之前接口中只能定义抽象方法,可以用default和static修饰方法,增加可维护性
(1)static
用static修饰接口中的方法,表示静态的方法,在子类中不能重写,只能通过接口来调用
(2)default
子类可以重写接口中被default修饰的方法(用 @Override验证 )
(3)子类可以不用重写接口中被static和default修饰的方法
如下图所示不重写,不会报错
六、常用接口
1.序列化接口(Serializable) 网络传输
2.Comparable接口 比较
3.Comparator接口
七、枚举
1.简介
在JDK1.5以后引入的,是一个
主要用途是将一组常量也就是离散的值组织起来,用于声明一组带标识符的常量。
eg: 表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。
2。自定义类实现枚举
在类的内部创建一组对象,通常使用public static final 修饰(视为常量)。接收对象的变量名通常全部大写,这是常量的命名规则。
可以提供属性,被private final 修饰;
将构造器私有化,可以提供get方法,不需要提供set方法,因为不可以被final修饰不可以被修改
3.enum关键字实现枚举
1. 使用enum关键字实现枚举默认继承java.lang.Enum类
2.必须在第一行声明枚举类对象,否则会报错
3. 有多个枚举对象时候,用","隔开,最后一个用";"结尾
4.可以提供私有的属性,以及私有的构造方法
5. 构造器
(1)如果是无参构造器,小括号可以省略
(2)如果有形参,必须显示的调用
八、内部类
1.成员内部类
1.介绍
顾名思义,定义在类的内部,与这个类的属性和方法平级,并且成员内部类没有用static修饰。
2.访问权限修饰符
private,默认,protected,public
3.如何创建内部类(实例化):
先创建一个外部类对象,然后通过外部类对象来实例化一个内部类对象。
4.可以访问外部类的成员
外部类名.this.成员
public class Outer {
private String name;
private int age;
//成员内部类Inner
class Inner{
private String name;
private int age;
public Inner(String name, int age) {
this.name = name;
this.age = age;
}
public void showInfor(){
System.out.println("Name: " + name+ " Age: " + age);
//访问外部类的成员:外部类名.this.成员
System.out.println(Outer.this.name+","+Outer.this.age);
}
}
//外部类的构造器
public Outer(String name, int age) {
this.name = name;
this.age = age;
}
public void showInfor(){
System.out.println("Name: " + name+ " Age: " + age);
}
public static void main(String[] args) {
//先创建一个外部类对象
Outer outer = new Outer("妈妈", 40);
//然后通过外部类对象来实例化一个内部类对象
Inner inner = outer.new Inner("儿子", 20);
inner.showInfor();
}
}
2.静态内部类
(1)介绍
定义在一个类的内部,与这个类的成员(属性、方法)平级,并且被static修饰的类。。
(2)访问权限修饰符
private,默认,protected,public
(3)实例化
创建内部类的对象:外部类名.内部类名 变量名 = new 外部类名.内部类()
(4)只能访问外部类的及静态变量
public class Outer {
private String name;
public Outer(String name) {
this.name = name;
}
public void showInfo(){
System.out.println(name);
}
static class Inner {
private String name;
public Inner(String name) {
this.name = name;
}
public void showInfo(){
System.out.println(name);
//不能直接访问外部类的成员
//System.out.println(Outer.this.name);
}
}
public static void main(String[] args) {
//创建内部类的对象:new 外部类名.内部类的构造器
Inner i = new Outer.Inner("son");
String name = i.name;
System.out.println(name);
i.showInfo();
}
}
3.局部内部类
(1)介绍
定义在一个代码段中
(2)访问修饰符
没有访问修饰符
(3)实例化
在当前方法中直接实例化
(4)作用域
这个类只能在当前方法中使用
public class Outer {
public static void main(String[] args) {
int a = 10;
System.out.println(a);
class Inner {
private String name;
public Inner(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Inner inner = new Inner("son");
System.out.println(inner.getName());
}
}
4,匿名内部类
(1)介绍
没有名字的内部类,通常配合其他的类或者接口一起使用,一般用来做方法的重写实现的
(2)语法格式
new 接口名/抽象类名/父类名(){
方法的重写
};
所以是 接口/抽象类/父类 的子类;相当于把子类赋给了父类也就是向上转型
一般不添加新的成员(方法和属性),因为及时添加了,得到的对象是向上转型后的对象,不能访问子类中的成员。
public class Outer {
public static void main(String[] args) {
A a = new A(){
//匿名内部类,提供成员属性
private String name;
//重写接口A里的抽象方法
public void showInfo(){
System.out.println("中国必胜");
}
//子类提供了独有的get方法
public String getName(){
return name;
}
};
//编译期间,看变量类型,因此能调用showInfo,运行期间看对象类型
a.showInfo();
//a.getName(); //编译期间 a里面根本没有getName方法,编译都不会通过
}
}
interface A{
public void showInfo();
}