面向对象
类与对象的关系
类是一种抽象的数据类型,它是对某一类事物整体描述。即抽象的概念。
例如:人、宠物、车,程序中即是Person类、Pet类、Car类
对象是抽象概念的具体实例。即一个体。
例如:张三、狗、奥迪
代码与现实相反,先有类后有对象。
类实例化后会返回一个自己的对象,通过new实例化。
package Demo;
public class Hello {
public static void main(String[] args) {
Student student1 = new Student();//创建对象
// 对象类型 对象名 对象值
Student student2 = new Student();
}
static class Student{//类最多就两点,属性和方法。
String name;
int age;
}
}
对象与引用的关系
引用 "指向" 对象
使用类类型、数组类型、接口类型声明出的变量,都可以指向对象,这种变量就是引用类型变量,简称引用。对象是通过引用来操作的。
在程序中,创建出对象后,直接使用并不方便,所以一般会用一个引用类型的变量去接收这个对象,这个就是所说的引用指向对象。
对象与方法的关系
静态方法
使用static修饰符修饰的方法,就是静态方法。
调用这种方法的时候,"可以"使用对象调用,也"可以"使用类来调用,但是推荐使用类进行调用,因为静态 方法是属于类的。(静态属性也是一样的)
package Demo;
public class Hello {
public static void main(String[] args) {
say();//使用static修饰的方法可以直接被调用,如果say方法在同一个包下的其他类中如在Student类中,则Student.say();
}
public static void say(){
System.out.println("Good game");//结果为Good game
}
}
Student.java
package object;
public class Student {
public void greet(){
System.out.println("I am ");
}
public static void he(){
System.out.println("he");
}
}
demo1.java
package object;
public class demo1 {
public static void main(String[] args) {
Student student1=new Student();
student1.greet();//I am
Student.he();//he
student1.he();//he
}
}
//虽然static方法he()可以被对象student1调用(idea不会提供快捷操作),但建议使用类Student调用。
而非static方法greet()使用对象student1调用。
非静态方法
没有使用static修饰符修饰的方法,就是非静态方法。
调用这种方法的时候,是"一定"要使用对象的。因为非静态方法是属于对象的。(非静态属性也是一样的)
在项目中方法更多的是非静态的,为了更好的封装,避免直接被外部调用。
package Demo;
public class Hello {
public static void main(String[] args) {
Hello s = new Hello();//创建对象,实例化方法所在的类
s.say();//调用对象中的方法
//new Hello().say(); 也可以,但更推荐上面那种写法
}
public void say(){
System.out.println("Good game");//结果为Good game
}
}
同一类中方法调用
假设同一个类中有a、b两个方法
-
a和b都是非静态方法,相互之间可以直接调用。
public void a(){ b(); } public void b(){ }
-
a和b都是静态方法,相互之间可以直接调用。
public static void a(){ b(); } public static void b(){ }
-
a静态方法,b是非静态方法,a方法中不能直接调用b方法,但是b方法中可以直接调用a方法. 静态方法不能调用非静态方法!
public static void a(){ b();//报错 } public void b(){ a(); }
值传递和引用传递
调用方法进行传参时,分为值传递和引用传递两种。
如果参数的类型是基本数据类型,那么就是值传递。
如果参数的类型是引用数据类型,那么就是引用传递。
值传递是实参把自己变量本身存的简单数值赋值给形参。
引用传递是实参把自己变量本身存的对象内存地址值赋值给形参。
所以值传递和引用传递本质上是一回事,只不过传递的东西的意义不同而已。
- 值传递
package Demo;
public class Hello {
public static void main(String[] args) {
int a=1;
System.out.println(a);//1
change(a);
System.out.println(a);//1
}//并没有改变a的值
public static void change(int x){//往深的说,a和x的内存地址不一样,改变后不影响各自的取值,与void无关
x=2;
}
}
- 引用传递
package Demo;
public class Hello {
public static void main(String[] args) {
Student a = new Student();
System.out.println(a.age);//0
change(a);
System.out.println(a.age);//2
}
public static void change(Student a) {
a.age = 2;
}
}
class Student{
int age;//初始值为0
}
构造器
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。
-
构造器的特点:
1.必须和类的名字相同
2.必须没有返回类型,也不能写void -
构造器的作用:
1.创建对象本质是调用构造器,当调用完构造器后才算new了一个对象。
2.给对象中的属性初始化赋值。
public class Student{
String name;
//构造器
public Student(){
name = "tom";
}
}
- 默认构造器
初学对象的时候代码中为什么没有构造器?因为在java中,即使我们在编写类的时候没有写构造器,编译之后也会自动添加一个无参构造器,这个无参构造器也被称为默认的构造器。
public class Student{
}//不会在代码中显示,但编译后查看class文件能在Student类中找到
- 构造器重载
除了无参构造器之外,很多时候我们还会使用有参构造器,在创建对象时候可以给属性赋值。
创建有参构造器后系统就不会生成无参构造器了,如果需要用就要自己写无参构造器出来。
Person.java
package Demo;
public class Person {
String name;
public Person(){//定义有参构造器不一定要把无参构造器也写出来
name="benson";
}
public Person(String name){
this.name=name;//this.name的name指的是Person的属性name,=右边的name指的是参数的name
}//this关键字指代当前所在类,这里是Person类
}
demo01.java
package Demo;
public class demo01 {
public static void main(String[] args) {
Person person = new Person();
System.out.println(person.name);//benson
Person person1 = new Person("LJS");//输入参数创建对象时会指向有参构造器
System.out.println(person1.name);//LJS
}
}
当存在多个有参构造器时,为了提高代码的复用性,可以使用this(参数);来指代对应的有参构造器,节省代码量。
Student.java
public class Student {
String name;
int age;
String address;
public void study() {
System.out.println(this.name+"在读书,年龄:"+this.age);
}
public Student() {
name = "student";
age = 18;
study();
}
public Student(String name, int age) {
this.name = name;
this.age = age;
study();
}
public Student(String name, int age,String address){
// this.name = name;
// this.age = age;
this(name,age);//替代上面的代码
this.address = address;
System.out.print("地址:"+this.address);
}
}
StudentTest.java
public class StudentTest {
public static void main(String[] args) {
Student student = new Student();
Student student1 = new Student("ben",20);
Student student2 = new Student("benson",22,"广东");
}
}
内存分析
JDK8后方法区从堆中改成存到本地内存中
封装
制造厂家为了方便我们使用电视,把复杂的内部细节全部封装起来,只给我们暴露简单的接口,比如:电源开关。
需要让用户知道的暴露出来,不需要让用户了解的全部隐藏起来。这就是封装。
封装是把一个类的各种属性和方法集成起来
封装的步骤
-
- 使用private修饰需要封装的成员变量。
-
- 提供一个公开的方法设置或者访问私有的属性
设置 通过set方法,命名格式: set属性名(); 属性的首字母要大写
访问 通过get方法,命名格式: get属性名(); 属性的首字母要大写
- 提供一个公开的方法设置或者访问私有的属性
//对象能在类的外部"直接"访问
public class Student{
public String name;
public void println(){
System.out.println(this.name);
}
}
public class Test{
public static void main(String[] args){
Student s = new Student();
s.name = "tom";
}
}
在类中一般不会把数据直接暴露在外部的,而使用private(私有)关键字把数据隐藏起来
public class Student{
private String name;
}
public class Test{
public static void main(String[] args){
Student s = new Student(); //编译报错,在类的外部不能直接访问类中的私有成员
s.name = "tom";
}
}
如果在类的外部需要访问这些私有属性,那么可以在类中提供对于的get和set方法,以便让用户在类的外部可以间接的访问到私有属性
//set负责给属性赋值
//get负责返回属性的值
public class Student{
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
public class Test{
public static void main(String[] args){
Student s = new Student();
s.setName("tom");
System.out.println(s.getName());
}
}
继承
- 继承的本质在于抽象。类是对对象的抽象,继承是对某一批类的抽象。
- 继承关系的俩个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来表示。
继承的意义在于减少有共性的类与类之间的代码重复
子类可以使用直接或间接父类的内容,但不能使用“叔叔辈”的内容
public class Student extends Person{ //Student是子类,Person是父类,student is a Person
}
- JAVA中类只有单继承,没有多继承,即除了object类,一个子类只能继承一个父类。ps:接口可以多继承
- 父类中的属性和方法可以被子类继承
父类中的属性和方法使用public修饰,在子类中继承后"可以直接"使用
父类中的属性和方法使用private修饰,在子类中继承后"不可以直接"使用,父类要提供get/set方法
父类中的构造器是不能被子类继承的,但是子类的构造器中,会隐式的调用父类中的无参构造器(默认使用super关键字)
用final修饰的类不能被继承,没有子类。final断子绝孙!
Object类
java中的每一个类都是"直接" 或者 "间接"的继承了Object类.所以每一个对象都和Object类有"is a"的关系。
从API文档中,可以看到任何一个类最上层的父类都是Object。(Object类本身除外)AnyClass is aObject。
Super关键字
子类继承父类之后,在子类中可以使用this来表示访问或调用子类中的属性或方法,使用super就表示访问或调用父类中的属性和方法。
- 访问父类中的属性
- 调用父类中的方法
- 调用父类的构造器
子类可能会用到父类的数据,所以会先调用父类的无参构造器以得到父类初始化的数据
-
- 父类无构造器
- 父类无构造器
-
- 显式地调用父类的有参构造器
- 显式地调用父类的有参构造器
super使用的注意的地方
- 用super调用父类构造方法,必须是构造方法中的第一个语句。
- super只能出现在子类的方法或者构造方法中。
- super 和 this 不能够同时调用构造方法。(因为this也是在构造方法的第一个语句)
Person.java
package object.extendsDemo.constrctorDemo;
public class Person {
public String name;
public int age;
public Person(){
System.out.println("父类无参构造");
}
public Person(String name,int age){
this.name=name;
this.age=age;
}
}
Student.java
package object.extendsDemo.constrctorDemo;
public class Student extends Person{
public Student(){
//隐式调用父类无参构造器
//super();可以写出来,但必须在第一行
System.out.println("子类无参构造");
}
public Student(String name,int age){
super(name,age);
}
}
StudentTest.java
package object.extendsDemo.constrctorDemo;
public class StudentTest {
public static void main(String[] args) {
Student s=new Student();
Student student=new Student("Benson",18);
System.out.println(student.name+":"+student.age);
}
}
方法重写override
涉及多态,理解就好
静态方法是类的方法,有static时因为b是B类定义的,所以b调用B类的方法,即有static看=左边
非静态方法是对象的方法,没有static时b调用的是对象的方法,因为b是A类new出来的对象,所以b调用A的方法
无static时看=右边
重写的前提是继承,子类重写父类的方法。只针对类的方法,与属性无关。非静态方法才算重写。private方法不能重写
而重载是在同一个类中,方法名相同,参数列表不同。
重写语法
- 方法名必须相同
- 参数列表必须相同
- 访问控制修饰符可以被扩大,但是不能被缩小: public>protected>default>private,如protected->public
- 抛出异常类型的范围可以被缩小,但是不能被扩大(本来欠1k,创建子类后不能欠1w,可以欠100)
ClassNotFoundException<Exception - 返回类型可以相同,也可以不同,如果不同的话,子类重写后的方法返回类型必须是父类方法返回类型的子类型
例如:父类方法的返回类型是Person,子类重写后的返回类可以是Person也可以是Person的子类型
- 权限修饰符
为什么要重写?
子类继承父类,继承了父类中的方法,但是父类中的方法并不一定能满足子类中的功能需要,所以子类中需要把方法进行重写。
重写也可以理解成子类的方法覆盖了父类同名的方法
Dog.java
package object.dogs;
public class Dog {
public void eat(){
System.out.println("吃狗粮");
}
public void drink(){
System.out.println("喝水");
}
public void lookhome(){
System.out.println("看家");
}
}
Husky.java
package object.dogs;
public class Husky extends Dog{
public void breakhome(){
System.out.println("拆家");
}
}
SharPei.java
package object.dogs;
public class SharPei extends Dog{
@Override
public void eat() {
super.eat();
System.out.println("吃骨头");
}
}
ChineseDog.java
package object.dogs;
public class ChineseDog extends Dog{
@Override
public void eat() {
System.out.println("吃剩饭");
}
}
DogTest.java
package object.dogs;
public class DogTest {
public static void main(String[] args) {
Husky husky=new Husky();
SharPei sharpei=new SharPei();
ChineseDog chineseDog=new ChineseDog();
husky.eat();
husky.drink();
husky.lookhome();
husky.breakhome();
sharpei.eat();
chineseDog.eat();
}
}
Employee.java
package object.extendsDemo.test;
public class Employee {
private int id;
private String name;
private double salary;
//构造器
public Employee(){
}
public Employee(int id,String name,double salary){
this.id=id;
this.name=name;
this.salary=salary;
}
//get/set
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//方法
public void work(){
System.out.println("工作");
}
public void eat(){
System.out.println("吃米饭");
}
}
Manager.java
package object.extendsDemo.test;
public class Manager extends Employee{
private double bonus;
public Manager(){
}
public Manager(int id,String name,double salary,double bonus){
super(id,name,salary);
this.bonus=bonus;
}
public double getBonus() {
return bonus;
}
public void setBonus(double bonus) {
this.bonus = bonus;
}
public void work(){
System.out.println("管理其他人");
}
}
Cook.java
package object.extendsDemo.test;
public class Cook extends Employee{
public Cook(){
}
public Cook(int id,String name,double salary){
super(id,name,salary);
}
@Override
public void work() {
System.out.println("炒菜");
}
}
EmployeeTest.java
package object.extendsDemo.test;
public class EmployeeTest {
public static void main(String[] args) {
Manager manager=new Manager(1,"mam",10000,5000);
System.out.println(manager.getSalary());
manager.work();
Cook cook=new Cook(2,"cook",8000);
cook.work();
System.out.println(cook.getSalary());
}
}
多态
多态即对象的多种形态
- 多态的应用场景:
有三种人注册账号,在代码中要怎么定义注册方法的形参呢?
不可能给每种人都写一遍同样的方法,因为程序的延展性不好,如果多了个辅导员的角色,那么又要再写一次注册的方法
此时可以用多态解决问题,让学生老师管理员的共同父类Person来当参数,这样就可以传入什么对象就使用相应对象的方法
传入学生的对象,就会使用学生的注册方法
多态的表现形式
父类类型 对象名=子类对象;
//即父类引用指向子类对象
多态的前提
1.有继承关系
2.父类引用指向子类对象
3.方法重写
Person.java
package object.polymorphism;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
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 void show(){
System.out.println(name+","+age);
}
}
Student.java
package object.polymorphism;
public class Student extends Person{
@Override
public void show() {
System.out.println("学生的信息为:"+getName()+","+getAge());
}
}
Teacher.java
package object.polymorphism;
public class Teacher extends Person{
@Override
public void show() {
System.out.println("教师的信息为:"+getName()+","+getAge());
}
}
Administrator.java
package object.polymorphism;
public class Administrator extends Person{
@Override
public void show() {
System.out.println("管理员的信息为:"+getName()+","+getAge());
}
}
Test.java
package object.polymorphism;
public class Test {
public static void main(String[] args) {
Student s=new Student();
s.setName("学生");
s.setAge(18);
Teacher t=new Teacher();
t.setName("老师");
t.setAge(30);
Administrator a=new Administrator();
a.setName("管理员");
a.setAge(33);
register(s);
register(t);
register(a);
}
public static void register(Person p){
//要求这个方法既能接收学生,又能接收老师,也能接受管理员
//可以把参数写成三者的父类
p.show();
}
}
多态的好处
使用父类型的对象作为参数,可以接收所有子类对象
加强代码的扩展性
多态的特点
- 调用成员变量的特点
编译看左边,运行也看左边 - 调用成员方法的特点
编译看左边,运行看右边
Animal.java
package object.polymorphism.t2;
public class Animal {
String name="动物";
public void show(){
System.out.println("Animal-show");
}
}
Cat.java
package object.polymorphism.t2;
public class Cat extends Animal{
String name="猫";
@Override
public void show() {
System.out.println("Cat-show");
}
}
Dog.java
package object.polymorphism.t2;
public class Dog extends Animal{
String name="狗";
@Override
public void show() {
System.out.println("Dog-show");
}
}
demo.java
package object.polymorphism.t2;
public class demo {
public static void main(String[] args) {
Animal a=new Cat();
//调用成员变量:编译看左边,运行也看左边的Animal类型的a
System.out.println(a.name);//动物
//调用成员方法:编译看左边,运行看右边 走Cat类的方法
a.show();//Cat-show
}
}
多态的缺点
不能调用子类特有的功能
//把上面的Cat类加多个方法
package object.polymorphism.t2;
public class Cat extends Animal{
String name="猫";
@Override
public void show() {
System.out.println("Cat-show");
}
public void catchMouse(){
System.out.println("抓老鼠");
}
}
回到demo.java,发现a没有Cat特有的catMouse方法
因为调用成员方法编译看左边,左边的Animal类中并没有catMouse方法,所以报错。
解决方法:
把a从Animal类型转回Cat类型,大转小,与强制转换类型相似
package object.polymorphism.t2;
public class demo {
public static void main(String[] args) {
Animal a=new Cat();
//调用成员变量:编译看左边,运行也看左边的Animal类型的a
System.out.println(a.name);//动物
//调用成员方法:编译看左边,运行看右边 走Cat类的方法
a.show();//Cat-show
if (a instanceof Cat){//不能乱转换,用instanceof判断对象是不是某某类型或者某某类型的子类
Cat b=(Cat)a;
b.catchMouse();
}else if (a instanceof Dog){
Dog d=(Dog) a;
}
}
}
代码块
局部代码块
局部:方法里面 代码块:大括号 局部代码块:写在方法里面的大括号
作用:提前结束变量的生命周期以节约内存(现在已经用不上了)
int b=2;写在局部代码块中,那么变量b就是局部变量
且它将会在局部代码块之后(第11行)在内存中消失,所以打印b会报错
构造代码块
写在类中成员位置的代码块
作用:提取构造器中重复的代码
demo3.java与demo2.java相比,将无参和有参构造器中相同的语句放到了构造代码块中
而且在创建对象时构造代码块会优先于构造方法执行
demo3.java
package object.codeblock;
public class demo3 {
private String name;
private int age;
{
System.out.println("name,age");
}
public demo3(){
System.out.println("空参");
}
public demo3(String name,int age){
this.name=name;
this.age=age;
System.out.println("有参");
}
}
demo3test.java
package object.codeblock;
public class demo3 {
private String name;
private int age;
{
System.out.println("name,age");
}
public demo3(){
System.out.println("空参");
}
public demo3(String name,int age){
this.name=name;
this.age=age;
System.out.println("有参");
}
}
但这种代码块也渐渐被淘汰了,因为如果有第三个构造器没有与前两个有相同的语句,那么在调用第三个构造器时还是会触发前两个构造器的重复语句
可以在无参中用this解决,也可以把重复语句放到一个函数里,用到时再调用函数。
静态代码块
通过static关键字修饰,随着类的加载而加载并且自动触发、只执行一次
构造代码块是每创建一个对象就会执行一次
作用:在类加载的时候,需要数据初始化时使用
比如学生信息管理系统中可以提前添加些学生信息进去
第二幅图也可以达到静态代码块的效果,它是写在main方法里的,但有弊端。
因为只要是方法就可能会被反复调用,造成图中的信息被反复添加。
静态导入包
静态导包就是java包的静态导入,用import static代替import,导入这个类里的静态方法。
好处:这种方法的好处就是可以简化一些操作
例如打印操作System.out.println(…);就可以将其写入一个静态方法print(…),在使用时直接print(…)就可以了。
但是这种方法建议在有很多重复调用的时候使用,如果仅有一到两次调用,不如直接写来的方便。
import static java.lang.Math.random;
import static java.lang.Math.PI;
public class Test {
public static void main(String[] args) {
//之前是需要Math.random()调用的,即System.out.println(Math.random());
System.out.println(random());
System.out.println(PI);
}
}
抽象类abstract
父类的work()方法描述的比较笼统,子类Student和Teacher应当要重写,但难免会出现子类忘了重写或者就不重写的情况
这时可以用抽象类来规避这种错误,也方便了描述
用abstract修饰成抽象方法,抽象方法没有方法体,子类必须重写,抽象方法所在的类必须声明为抽象类。
- 抽象类不能创建对象(实例化)
反过来想,如果抽象类能创建对象,那它的对象调用抽象方法,但抽象方法没有方法体,调用了寂寞,所以抽象类不能实例化。 - 抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
- 抽象类可以有构造方法
抽象类不能被实例化,那为什么还可以有构造方法呢?
作用:当创建子类对象时给子类对象的属性赋值
- 抽象类的子类要么重写所有抽象方法,要么也是抽象类
意义:
抽象类更多用于团队开发,比如规范某一方法如eat方法的重写。方便团队成员对eat方法的重写,而不用找其他成员的对eat方法是怎么描述的
接口interface
接口就是一种规则
兔子不会游泳,父类Animal就不能有游泳swim方法。但又不适合狗和青蛙各写一个swim方法,因为可能不规范
而且如果以后还有其他子类也会游泳那又要写多一遍swim方法。此时就可以用接口来定义游泳的规则
与抽象类相比,接口就显得没有跟子类没有太紧密的联系,
接口只是规则,只要能游泳的都能用,机器狗也行
- 接口不能实例化
- 接口的子类(实现类)要么重写接口所有的抽象方法,要么是抽象类
- 接口和类的关系可以是单实现,也可以是多实现
public class 类名 implements 接口名1,接口名2{}
- 实现类可以继承一个类的同时实现多接口
public class 类名 extends 父类 implements 接口1,接口2{}
Animal.java
package object.interfaces;
public abstract class Animal {
private String name;
private int age;
public Animal(){}
public Animal(String name,int age){
this.name=name;
this.age=age;
}
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 abstract void eat();
}
Swim.java
package object.interfaces;
public interface Swim {
public abstract void swim();
}
Frog.java
package object.interfaces;
public class Frog extends Animal implements Swim{
public Frog() {
}
public Frog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("青蛙在吃虫子");
}
@Override
public void swim() {
System.out.println("蛙泳");
}
}
Rabbit.java
package object.interfaces;
public class Rabbit extends Animal{
public Rabbit() {
}
public Rabbit(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println("兔子在吃胡萝卜");
}
}
Test.java
package object.interfaces;
public class Test {
public static void main(String[] args) {
Frog f=new Frog("Frog",7);
System.out.println(f.getName()+","+f.getAge());
f.eat();
f.swim();
Dog d=new Dog();
d.eat();
d.swim();
Rabbit r=new Rabbit();
r.eat();
}
}
接口成员的特点
实现多接口的情况下如果有同名的方法,那么只需要重写一次即可。
实现有继承关系的接口时要重写它包括父接口的所有抽象方法
Person.java
package object.interfaces.i3;
public abstract class Person {
private String name;
private int age;
public Person(){}
public Person(String name,int age){
this.name=name;
this.age=age;
}
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;
}
}
Athlete.java
package object.interfaces.i3;
public abstract class Athlete extends Person{
public Athlete() {
}
public Athlete(String name, int age) {
super(name, age);
}
public abstract void Study();
}
Coach.java
package object.interfaces.i3;
public abstract class Coach extends Person{
public Coach() {
}
public Coach(String name, int age) {
super(name, age);
}
public abstract void teach();
}
English.java
package object.interfaces.i3;
public interface English {
public abstract void speakEnglish();
}
PingPongAthlete.java
package object.interfaces.i3;
public class PingPongAthlete extends Athlete implements English{
public PingPongAthlete() {
}
public PingPongAthlete(String name, int age) {
super(name, age);
}
@Override
public void Study() {
System.out.println(this.getName()+"在学打乒乓球");
}
@Override
public void speakEnglish() {
System.out.println(this.getName()+"在说英语");
}
}
PingpongCoach.java
package object.interfaces.i3;
public class PingpongCoach extends Coach implements English{
public PingpongCoach() {
}
public PingpongCoach(String name, int age) {
super(name, age);
}
@Override
public void teach() {
System.out.println(this.getName()+"在教打乒乓球");
}
@Override
public void speakEnglish() {
System.out.println(this.getName()+"在说英语");
}
}
BasketballAthlete.java
package object.interfaces.i3;
public class BasketballAthlete extends Athlete{
public BasketballAthlete() {
}
public BasketballAthlete(String name, int age) {
super(name, age);
}
@Override
public void Study() {
System.out.println(this.getName()+"在学打篮球");
}
}
BasketballCoach.java
package object.interfaces.i3;
public class BasketballCoach extends Coach{
public BasketballCoach() {
}
public BasketballCoach(String name, int age) {
super(name, age);
}
@Override
public void teach() {
System.out.println(this.getName()+"在教打篮球");
}
}
Test.java
package object.interfaces.i3;
public class Test {
public static void main(String[] args) {
PingPongAthlete ppa=new PingPongAthlete("马龙",27);
System.out.println("乒乓球运动员:"+ppa.getName()+","+ppa.getAge());
ppa.Study();
ppa.speakEnglish();
BasketballAthlete bba=new BasketballAthlete("郭艾伦",26);
System.out.println("篮球运动员:"+bba.getName()+","+bba.getAge());
bba.Study();
PingpongCoach ppc=new PingpongCoach("刘国梁",45);
System.out.println("乒乓球教练:"+ppc.getName()+","+ppc.getAge());
ppc.teach();
ppc.speakEnglish();
BasketballCoach bbc=new BasketballCoach("姚明",36);
System.out.println("篮球教练:"+bbc.getName()+","+bbc.getAge());
bbc.teach();
}
}
接口的默认方法default
当项目版本更新后需要在接口那新增10个功能(接口升级),但如果往接口中新增抽象方法,那它的实现类就会报错
要怎么在接口升级的同时又不会使其实现类报错呢?
这里就用到了JDK8之后的特性,允许接口中的方法有方法体,这种方法用default修饰,且不能省略
子类不一定要重写default方法,但重写时要把default修饰词去掉
实现多接口时若存在相同名字的默认方法,则要重写默认方法
因为interImpl类在多实现接口inter1和2时,两接口都有同名的默认方法,但interImpl没有重写,所以报错
接口的静态方法
接口的静态方法不需要也不能重写,可以被测试类直接调用
接口的私有方法
接口中两个默认方法中有重复的代码,可以将它们提取到方法log中,然后默认方法再调用log方法
如果log方法中记录的是不适合分享的数据,可以把log变成私有方法private
如果默认方法改成静态方法,则把log变成私有静态方法private static
接口多态
USB.java
public interface USB {
public void work();//接口的方法是抽象的,可以不用abstract修饰
}
Mouse.java
public class Mouse implements USB{
@Override
public void work(){
System.out.println("鼠标已接入USB");
}
}
Keyboard.java
public class Keyboard implements USB{
@Override
public void work() {
System.out.println("键盘已接入USB");
}
}
Computer.java
public class Computer {
public void connect(USB usb){
usb.work();
}
}
ComputerTest.java
public class ComputerTest {
public static void main(String[] args) {
Computer computer = new Computer();
Mouse mouse = new Mouse();
Keyboard keyboard = new Keyboard();
computer.connect(mouse);//鼠标已接入USB
computer.connect(keyboard);//键盘已接入USB
}
}
适配器模式
当一个接口抽象类方法比较多时,但我写一个类时又只需要它的一部分方法,此时可以新建一个空实现类来作为接口的适配器
然后再让要写的类继承适配器即可,这样一来我想用哪个方法就用哪个方法
内部类
类的五大成员:属性、方法、构造器、代码块、内部类
内部类:在一个类的里面再定义一个类
在Outer类里定义Inner类,Inner就是内部类,Outer就是外部类
与这两类都无关的就叫外部其他类
应用场景:
汽车的发动机、ArrayList的迭代器、人的心脏
一般会这么写
但发动机是一个独立的个体,跟车本身还是有区别的
可以把发动机的属性放到一个类里,但单纯只是一个普通类的话,在题意的背景下,发动机需要依赖车存在,没什么实际意义
这时就可以把发动机的类定义为内部类
内部类表示的事物是外部类的一部分,内部类单独出现通常没有意义
内部类的特点
- 内部类可以直接访问外部类的成员,包括私有的
- 外部类要访问内部类的成员,要创建对象
package object.InnerClass.t1;
public class Car {
public String carName;
public int carAge;
public String carColor;
public void show(){
Engine e=new Engine();//需要创建对象才能访问内部类成员
System.out.println(carAge+","+e.engineAge);
}
class Engine{
public String engineName;
public int engineAge;
public void show(){
System.out.println(engineName+","+carName);
//可以直接访问外部类的成员
}
}
}
成员内部类
一般成员内部类不用static修饰,成员内部类的地位与成员变量相同
静态内部类
静态内部类是一种特殊的成员内部类
局部内部类
匿名内部类
真正的没有名字的类是紫色圈圈那一块,与Swim()是实现关系,new的是紫色圆圈圈出来的没名字的类的对象
书写格式的整体叫做匿名内部类的对象更为贴切
- 应用场景
匿名内部类可以是成员内部类(如图),也可以是局部内部类。
练习
Role.java
package object.pk;
import java.util.Random;
public class Role {
private String name;
private int hp;
private int atk;
private String character;
private String skill;
public Role() {
}
public Role(String name) {
this.name = name;
hp = 100;
atk = 10;
setCharacter(index_chara);
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setHp(int hp) {
this.hp = hp;
}
public int getHp() {
return hp;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
String[] characters = {"开朗", "沉稳", "浮躁", "胆小"};
Random r_character = new Random();
int index_chara = r_character.nextInt(characters.length);//随机数为0~characters.length-1的下标
public String getCharacter() {
return character;
}
public void setCharacter(int index_chara) {
this.character = characters[index_chara];
switch (this.getCharacter()) {
case "开朗":
this.setHp(120);
this.setAtk(20);
break;
case "沉稳":
this.setHp(130);
break;
case "浮躁":
this.setHp(90);
this.setAtk(25);
break;
case "胆小":
this.setHp(140);
this.setAtk(5);
break;
}
}
public String getSkill() {
return skill;
}
public void setSkill(String skill) {
this.skill = skill;
}
private int ap;
String[] skills_pikachuu = {"电光火石", "钢铁摆尾", "十万伏特"};
String[] skills_firedradon = {"火球", "疯狂乱抓", "火焰喷射"};
public void Skills(String name,int x) {
if (name == "皮卡丘") {
this.skill = skills_pikachuu[x];
switch (skills_pikachuu[x]) {
case "电光火石":
this.setAp(10);
break;
case "钢铁摆尾":
this.setAp(20);
break;
case "十万伏特":
this.setAp(30);
break;
}
} else if (name == "小火龙") {
this.skill = skills_firedradon[x];
switch (skills_firedradon[x]) {
case "火球":
this.setAp(10);
break;
case "疯狂乱抓":
this.setAp(20);
break;
case "火焰喷射":
this.setAp(30);
break;
}
}
}
public int getAp() {
return ap;
}
public void setAp(int ap) {
this.ap = ap;
}
public void attack (Role role){
Random r_skill = new Random();
int index_skill = r_skill.nextInt(skills_pikachuu.length);
Skills(this.getName(),index_skill);
int damage = this.getAtk() +this.getAp();
int remainHp = role.getHp() - damage;
remainHp = remainHp < 0 ? 0 : remainHp;//剩余血量不小于0
role.setHp(remainHp);
System.out.println(this.getName() + "对" + role.getName() + "使用"+this.skill+",造成了" + damage + "点伤害,"
+ role.getName() + "剩余" + remainHp + "点hp。");
}
}
GameTest.java
package object.pk;
public class GameTest {
public static void main(String[] args) {
Role r1=new Role("皮卡丘");
Role r2=new Role("小火龙");
System.out.println("皮卡丘VS小火龙");
System.out.println("皮卡丘:");
System.out.println("性格:"+r1.getCharacter());
System.out.println("HP:"+r1.getHp());
System.out.println("攻击力:"+r1.getAtk());
System.out.println("=============================");
System.out.println("小火龙:");
System.out.println("性格:"+r2.getCharacter());
System.out.println("HP:"+r2.getHp());
System.out.println("攻击力:"+r2.getAtk());
System.out.println("===========比赛开始===========");
while (true){
r1.attack(r2);
if (r2.getHp()==0){
System.out.println(r1.getName()+"打败了"+r2.getName()+"!");
break;
}
r2.attack(r1);
if (r1.getHp()==0){
System.out.println(r2.getName()+"打败了"+r1.getName()+"!");
break;
}
}
}
}
各种类的意义