Java基础 - 6 - 面向对象(二)

Java基础 - 6 - 面向对象(一)-CSDN博客


二. 面向对象高级

2.1 static

        static叫做静态,可以修饰成员变量、成员方法

2.1.1 static修饰成员变量

        成员变量按照有无static修饰,分为两种:类变量、实例变量(对象的变量)

        类变量(静态成员变量):有static修饰,属于类,与类一起加载一次,在计算机内存里只有一份,会被类的全部对象共享        类名.类变量

        实例变量(对象的变量):无static修饰,属于每个对象的,每个对象都有一份        对象.实例变量

//demo
public class demo {
    public static void main(String[] args) {
        //类变量 通过类名.类变量的方式访问(推荐)
        Student.name = "张三";
        //对象.类变量(不推荐)
        Student s1 = new Student();
        s1.name= "李四";
        Student s2 = new Student();
        s2.name= "王五";

        //因为类变量在计算机中只有一份,被共享
        System.out.println(Student.name);  //王五
        System.out.println(s1.name);  //王五
        System.out.println(s2.name);  //王五

        //实例变量
        s1.age = 20;
        s2.age = 22;
        System.out.println(s1.age);  //20
        System.out.println(s2.age);  //22
    }
}

//Student
public class Student {
    //类变量
    static String name;
    //实例变量
    int age;
}

类变量的应用场景

        在开发中,如果某个数据只需要一份,且希望能够被共享(访问、修改),则该数据可以定义为类变量来记住

//demo
public class demo {
    public static void main(String[] args) {
        User u1 = new User();
        User u2 = new User();
        User u3 = new User();
        User u4 = new User();

        System.out.println(User.number);  //4
    }
}

//User
public class User {
    //类变量(通常用public修饰)
    public static int number;

    public User(){
        //注意:在同一个类中,访问自己的类变量,才可以直接不写类名
        User.number++;  //在这里可以直接写成 number++;
    }
}

2.1.2 static修饰成员方法

        成员方法的分类按照有无static修饰,可以分为类方法和实例方法

                类方法:有static修饰的成员方法,属于类

                实例方法:无static修饰的成员方法,属于对象

//demo
public class demo {
    public static void main(String[] args) {
        //类方法的使用
        //类名.类方法(推荐)
        Student.printHelloWorld();
        //对象.类方法(不推荐)
        Student s1 = new Student();
        s1.printHelloWorld();

        //实例方法的使用
        s1.score=70;
        s1.printPass();
        //不能直接用类名.实例方法(会报错)
    }
}

//Student
public class Student {
    double score;
    //类方法(有static,通常用public修饰)
    public static void printHelloWorld(){
        System.out.println("Hello World");
        System.out.println("Hello World");
    }
    //实例方法(对象的方法)
    public void printPass(){
        System.out.println("成绩"+((score>=60)?"及格":"不及格"));
    }
}

 补充:main方法——是一个类方法

类方法的应用场景

        类方法最常见的应用场景是做工具类

        工具类是什么?

                工具类中的方法都是一些类方法,每个方法都是用来完成一个功能的,工具类是给开发人员共同使用的

        使用类方法来设计工具类有啥好处?

                提高代码的复用;调用方便,提高开发效率

        为什么工具类中的方法要用类方法,而不用实例方法?

         · 实例方法需要创建对象来调用,此时对象只是为了调用方法,对象占内存,这样会浪费内存

         · 类方法,直接用类名调用即可,调用方便,也节省内存

        注意:

                工具类没有创建对象的需求,建议将工具类的构造器进行私有

//muUtil  是一个工具类
public class muUtil {

    //将工具类的构造器进行私有
    private muUtil(){

    }

    public static String createCode(int n){
        String str = "";
        Random r = new Random();
        for (int i = 0; i < n; i++) {
            //怎么随机生成一个随机字符、可能是数字、大写字母、小写字母?
            //思路:随机一个0-1-2,0表示数字、1表示大写字母、2表示小写字母
            int type = r.nextInt(3);//可以产生0-1-2的随机数
            if(type == 0){
                //0表示随机一个数字
                str += r.nextInt(10);//0-9
            }else if(type == 1){
                //1表示随机大写字母 A 65 Z 65+25=90
                str += (char)(r.nextInt(26)+65);
            }else{
                //2表示随机小写字母 a 97 z 97+25=122
                str += (char)(r.nextInt(26)+97);
            }
        }
        return str;
    }
}

//登录界面  4位验证码  LoginDemo
public class LoginDemo {
    public static void main(String[] args) {
        int n = 4;
        System.out.println("登录验证码:" + muUtil.createCode(n));
    }
}

//注册界面  6位验证码  RegisterDemo
public class RegisterDemo {
    public static void main(String[] args) {
        int n = 6;
        System.out.println("注册验证码:" + muUtil.createCode(n));
    }
}

使用类方法和实例方法的注意事项

        · 类方法中可以直接访问类成员,不可以直接访问实例成员

        · 实例方法中既可以直接访问类成员,也可以直接访问实例成员

        · 实例方法中可以出现this关键字,类方法中不可以出现this关键字

//Student
public class Student {
    //类变量
    static String school;
    //实例变量
    double score;
    //类方法
    public static void printHello() {

    }
    //实例方法
    public void printIsPass() {

    }

    1.类方法可以直接访问类的成员,不可以直接访问实例成员
    //类方法Test1
    public static void Test1() {
        //类方法可以直接访问类的成员
        //同一个类中,访问类成员,可以省略类名
        Student.school = "清华";  //school = "清华"
        Student.printHello();  //printHello2();

        //类方法不可以直接访问实例成员(因为实例成员属于对象)
        //System.out.println(score);  报错
        //printIsPass();   报错

        //类方法中不可以出现this关键字
        //System.out.println(this);   报错
    }

    2.实例方法中既可以直接访问类成员,也可以直接访问实例成员
    3.实例方法中可以出现this关键字,类方法中不可以出现this关键字
    //实例方法Test2
    public void Test2(){
        //实例方法中既可以直接访问类成员
        school = "北大";
        printHello();

        //实例方法也可以直接访问实例成员
        System.out.println(score);
        printIsPass();

        //实例方法中可以出现this关键字
        System.out.println(this);
    }
}

2.1.3 代码块*

        代码块是类的五大成分之一(成员变量、构造器、方法、代码块、内部类)

        代码块按照有无static划分为两类:静态代码块、实例代码块

静态代码块

        格式:static {}

        特点类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次

        作用:完成类的初始化,例如:对类变量的初始化赋值

实例代码块

        格式:{}

        特点每次创建对象时,执行实例代码块,并在构造器前执行

        作用:和构造器一样,都是用来完成对象的初始化,例如:对实例变量的初始化赋

//Student
public class Student {
    static int number = 80;
    static String school;
    
    int age;

    //静态代码块
    static {
        System.out.println("静态代码块执行~~~");
        school = "清华";
    }

    //实例代码块
    {
        System.out.println("实例代码块执行~~~");
        //age = 18; //实例变量初始化赋值意义不大(不可能所有人年龄一致)
    }

    public Student(){
        System.out.println("无参数构造器执行~~~");
    }

    public Student(String name){
        System.out.println("有参数构造器执行~~~");
    }
}


//demo
public class demo {
    public static void main(String[] args) {
        System.out.println(Student.number);
        System.out.println(Student.number);
        System.out.println(Student.number);

        System.out.println(Student.school); //清华

        System.out.println("--------------------");

        //输出结果
        //实例代码块执行~~~
        //无参数构造器执行~~~
        Student s1 = new Student();

        System.out.println("--------------------");

        //输出结果
        //实例代码块执行~~~
        //有参数构造器执行~~~
        Student s2 = new Student("张三");

        
        System.out.println(s1.age);  //18
        System.out.println(s2.age);  //18
    }
}

2.1.4 单例设计模式

设计模式:一个问题有很多解法,其中一种是最优的,这个最优解法被人总结后称之为设计模式

设计模式有20多种,对应20多种软件开发中会遇到的问题

设计模式学习思路:这个设计模式解决什么问题?这个设计模式怎么写?

单例设计模式

        确保一个类只有一个对象

单例设计模式的应用场景

        任务管理器、runtime(获取运行时对象)……

单例设计模式的好处

        可以避免浪费内存

饿汉式单例设计模式(拿对象时,对象早就创建好了)的写法

       · 把类的构造器私有

       · 定义一个类变量记住类的一个对象

       · 定义一个类方法,放回对象

懒汉式单例设计模式(拿对象时,才开始创建对象)的写法

       · 把类的构造器私有

       · 定义一个类变量用于存储对象

       · 提供一个类方法,保证返回的是用一个对象

//A
//饿汉式单例  频繁使用建议选择饿汉式
public class A {
    //2.定义一个类变量记住类的对象
    private static A a = new A();
    //1.私有类的构造器
    private A(){

    }
    //3.定义一个类方法返回类的对象a
    public static A getObject(){
        return a;
    }
}

//B
//懒汉式单例
public class B {
    //2.定义一个类变量,用于存储这个类的一个对象
    private static B b;
    //1.私有类的构造器
    private B(){

    }
    //3.定义一个类方法,这个方法要保证第一次调用时才创建一个对象,后面调用时都会用着同一个对象返回
    public static B getInstance(){
        if(b == null){
            b = new B();
        }
        return b;
    }
}

//demo
public class demo {
    public static void main(String[] args) {
        A a1 = A.getObject();
        A a2 = A.getObject();
        System.out.println(a1);  //输出两个地址相同
        System.out.println(a2);  //输出两个地址相同

        B b1 = B.getInstance();  //第一次创建对象
        B b2 = B.getInstance();
        System.out.println(b1);  //输出两个地址相同
        System.out.println(b2);  //输出两个地址相同
    }
}

2.2 继承 

        Java中提供一个关键字extends,用这个关键字可以让一个类和另一个类建立起父子关系

// A称为父类(基类或超类)   B称为子类(派生类)

public class B extends A{

}

 继承的特点

        · 子类能够继承父类的非私有成员(成员变量、成员方法)

        · 继承后对象的创建:子类的对象是由子类、父类共同完成的

//A 父类
public class A {
   //公开成员
    public int i;
    public void print1(){
        System.out.println("print1111111");
    }

    //私有成员
    private int j;
    private void print2(){
        System.out.println("print2222222");
    }
}

//B 子类
public class B extends A{
    private int x;
    public int y;

    //子类是可以继承父类的非私有成员
    public void print3(){
        System.out.println(i);
        print1();

        //System.out.println(j);    报错
        //print2();    报错
    }
}

//demo
public class demo {
    public static void main(String[] args) {
        B b = new B();
        System.out.println(b.i);
        System.out.println(b.y);
        //System.out.println(b.x);  报错
        //System.out.println(b.j);  报错
        b.print1();
        b.print3();
        //b.print2();  报错
    }
}

 继承的好处

        减少重复代码的编写

//People
public class People {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//Teacher
public class Teacher extends People{
    private String skill;

    public String getSkill() {
        return skill;
    }

    public void setSkill(String skill) {
        this.skill = skill;
    }

    public void printInfo(){
        System.out.println(getName());
        System.out.println(getSkill());
    }
}

//demo
public class demo {
    public static void main(String[] args) {
        Teacher t = new Teacher();
        t.setName("张三");
        t.setSkill("Java python");
        System.out.println(t.getName());
        System.out.println(t.getSkill());
        System.out.println("====================");
        t.printInfo();
    }
}

2.2.1 权限修饰符

        权限修饰符是用来限制类中的成员(成员变量、成员方法、构造器、代码块……)能够被访问的范围

修饰符在本类中同一个包下的其他类里任意包下的子类里任意包下的任意类里
private
缺省
protected√(要求继承,要子类才可以访问,子类对象不可以)
public

2.2.2 单继承

        Java是单继承的,Java中的类不支持多继承,但是支持多层继承

2.2.3 Object类

        object类是java所有类的祖宗类,我们写的任何一个类,其实都是object的子类或子孙类

2.2.4 方法重写

        当子类觉得父类的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写

注意:

1.重写后,方法的访问,Java会遵循就近原则

2.重写小技巧:使用Override注解,它可以指定Java编译器,检查我们方法重写的格式是否正确,代码可读性会更好

3.子类重写父类方法时,访问权限必须大于或等于父类该方法的权限(public>protected>缺省)

4.重写的方法返回值类型,必须与重写方法的返回值类型一样或范围更小

5.私有方法(private)、静态方法(static)不能被重写,如果重写会报错的

//A
public abstract class A {
    protected void print1(){
        System.out.println("111");
    }

    public void print2(int a, int b){
        System.out.println("1111111");
    }
}

//B
public class B extends A{
    //方法重写
    @Override //可读性好 检查格式是否正确
    public void print1(){   //子类重写父类方法时,访问权限必须大于或等于父类该方法的权限
        System.out.println("666");
    }
    @Override
    //方法重写
    public void print2(int a,int b){
        System.out.println("6666666");
    }
}

//demo
public class demo {
    public static void main(String[] args) {
        B b = new B();
        b.print1();
        b.print2(1,2);
    }
}

方法重写在开发中的常见应用场景

        子类重写Object类的toString()方法,以便返回对象的内容

//Student
public class Student {
    private String name;
    private int age;

//    @Override
//    public String toString(){
//        String str = getName()+"今年"+getAge()+"岁";
//        return str;
//    }

    //快捷方式:右键 -> 生成 -> toString()
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public Student() {
    }

    public Student(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;
    }
}

//demo
public class demo {
    public static void main(String[] args) {
        Student s1 = new Student("张三",18);
        System.out.println(s1.toString()); //输出地址
        System.out.println(s1); //不加.toString()默认.toString()  输出地址
    }
}

2.2.5 子类访问成员的特点

        在子类方法中访问其他成员(成员变量、成员方法),是依照就近原则的(先找子类的局部范围,再找子类的成员范围,最后找父类的成员范围,没找到则报错)

        如果子父类中出现了重名的成员,优先子类的,此时一定要使用父类的使用super关键字,指定访问父类的成员:super.父类成员变量/父类成员方法

2.2.6 子类构造器的特点

        子类的全部构造器,都会先调用父类的构造器,再执行自己

子类构造器是如何实现调用父类构造器的?

        · 默认情况下,子类全部构造器的第一行代码都是super()(写不写都有),它会调用父类的无参数构造器

        · 如果父类没有无参数构造器 ,则必须在子类构造器的第一行手写super(…),指定去调用父类的有参数构造器

//父类中存在无参数构造器,都不写也是默认存在
class F{
    public F(){
        System.out.println("==父类的无参数构造器执行了==");
    }
}

class Z extends F{
    public Z(){
        //super(); //写不写都默认存在
        System.out.println("==子类的无参数构造器执行了==");
    }

    public Z(int a){
        //super(); //写不写都默认存在
        System.out.println("==子类的有参数构造器执行了==");
    }
}

public class demo {
    public static void main(String[] args) {
        Z z = new Z();  //先输出==父类的无参数构造器执行了==  再输出==子类的无参数构造器执行了==
        Z z2 = new Z(1);  //先输出==父类的无参数构造器执行了==  再输出==子类的有参数构造器执行了==
    }
}





//父类中存在有参数构造器,不存在无参数构造器,子类一定要写super()才能不报错
class F{
    public F(int m){
        System.out.println("==父类的有参数构造器执行了==");
    }
}

class Z extends F{
    public Z(){
        super(1);
        System.out.println("==子类的无参数构造器执行了==");
    }

    public Z(int a){
        super(1);
        System.out.println("==子类的有参数构造器执行了==");
    }
}

public class demo {
    public static void main(String[] args) {
        Z z = new Z();  //先输出==父类的有参数构造器执行了==  再输出==子类的无参数构造器执行了==
        Z z2 = new Z(1);  //先输出==父类的有参数构造器执行了==  再输出==子类的有参数构造器执行了==
    }
}

子类构造器的应用场景

super(…)调用父类有参数构造器的常见应用场景是为对象中包含父类这部分的成员变量进行赋值

class Teacher extends People{
    private String skills;

    public Teacher(String name, int age, String skills) {
        super(name, age);  //!!!!!!子类构造器调用父类构造器
        this.skills = skills;
    }

    public String getSkills() {
        return skills;
    }

    public void setSkills(String skills) {
        this.skills = skills;
    }
}

class People{
    private String name;
    private int age;

    public People() {
    }

    public People(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 class demo {
    public static void main(String[] args) {
        Teacher t = new Teacher("张三",33,"Java");
        System.out.println(t.getName());
        System.out.println(t.getAge());
        System.out.println(t.getSkills());
    }
}

2.2.7 this(…)调用兄弟构造器

         任意类的构造器中,是可以通过this(…)去调用该类的其他构造器的

        不能在一个构造器中同时写this()和super()

class Student{
    private String name;
    private int age;
    private String School;

    public Student() {
    }

    public Student(String name, int age){
        //this.name = name;
        //this.age = age;
        //this.School = "无";
        //this(…)调用兄弟构造器,可以简化代码
        this(name,age,"无");  //不能在一个构造器中同时写this()和super()
    }

    public Student(String name, int age, String school) {
        this.name = name;
        this.age = age;
        this.School = school;
    }

    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 String getSchool() {
        return School;
    }

    public void setSchool(String school) {
        this.School = school;
    }
}

public class demo {
    public static void main(String[] args) {
        Student s1 = new Student("张三",18,"清华");
        //如果学生没写学校,默认无
        Student s2 = new Student("李四",18);
        System.out.println(s2.getName());
        System.out.println(s2.getAge());
        System.out.println(s2.getSchool());
    }
}

2.3 多态

        多态是在继承/实现情况下的一种现象,表现为:对象多态、行为多态

//People 父类
public class People {
    public String name = "父类名称";

    public void run(){
        System.out.println("人在跑步");
    }
}

//Student
public class Student extends People{
    public String name = "学生名称";
    
    @Override
    public void run() {
        System.out.println("学生在跑步");
    }
}

//Teacher
public class Teacher extends People{
    public String name = "老师名称";

    @Override
    public void run() {
        System.out.println("老师在跑步");
    }
}

//demo
public class demo {
    public static void main(String[] args) {
        //对象多态
        People p1 = new Student();
        People p2 = new Teacher();

        //行为多态
        p1.run();  //识别技巧:编译看左边父类(看父类有没有该方法,没有则报错),运行看右边子类
        p2.run();

        System.out.println(p1.name);    //父类名称   原因是变量没有多态性
        System.out.println(p2.name);    //父类名称
    }
}

多态的前提

        有继承/实现关系;存在父类引用子类对象;存在方法重写 

多态的注意事项

        多态是对象、行为的多态,Java中的属性(成员变量)不谈多态

使用多态的好处

        · 在多态形式下,右边对象是解耦合的,更便于拓展和维护

        · 定义方法时,使用父类类型的形参,可以接收一切子类对象,扩展性更强、更便利

使用多态的弊端

        多态下不能直接调用子类的独有功能

多态下的类型转换问

        自动类型转换:父类 变量名 = new  子类();

        强制类型转换:子类 变量名 = (子类)父类变量

强制类型转换的注意事项:

        · 存在继承/实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错

        · 运行时,如果发现对象的真实类型与强制后的类型不同,就会报类型转换异常(ClassCastException)

       ·  强制转换前,Java建议:使用instanceof关键字,判断当前对象的真实类型,在进行强转

//People 父类
public class People {
    public void run(){
        System.out.println("人在跑步");
    }
}

//Student
public class Student extends People{
    @Override
    public void run() {
        System.out.println("学生在跑步");
    }

    public void learn(){
        System.out.println("学生要学习");
    }
}

//Teacher
public class Teacher extends People{
    @Override
    public void run() {
        System.out.println("老师在跑步");
    }

    public void teach(){
        System.out.println("老师要教书");
    }
}


//demo
public class demo {
    public static void main(String[] args) {
        //好处1:可以实现解耦,右边对象可以随意切换Student()
        People p1 = new Student();
        p1.run();

        //强制类型转换
        Student s = (Student)p1;
        s.learn();

        //强制类型转换可能存在的问题,编译阶段有继承或实现关系就可以强制转换,但在运行时可能出现类型转换异常
        //Teacher t = (Teacher)p1;
        //t.teach();  //ClassCastException(类型转换异常),因为Teacher类型不能指向Student()

        if(p1 instanceof Student){
            Student s2 = (Student)p1;
            s2.learn();
        }else{
            Teacher t2 = (Teacher)p1;
            t2.teach();
        }
    }
}

2.4 final

        final关键字是最终的意思,可以修饰(类、方法、变量)

                修饰类:该类被称为最终类,特点是不能被继承

                修饰方法:该方法被称为最终方法,特点是不能被重写

                修饰变量:该变量只能被赋值一次

final修饰变量的注意事项

        · final修饰基本类型的变量,变量存储的数据不能被改变

        · final修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的

public class demo {
//常量:用static final修饰的类变量是常量。常量建议名称全部大写,多个单词下划线连接
    //用final修饰成员变量-类变量,要直接赋值,否则报错;二次赋值也报错
    public static final String SCHOOL_NAME = "清华";
    //public static final String SCHOOL_NAME;  报错(没有直接赋值)

    //用final修饰成员变量-实例变量,要直接赋值,否则报错;二次赋值也报错
    //private final String name = "张三";  //用final修饰实例变量无意义,导致实例变量的值无法修改
    //private final String name;  报错(没有直接赋值)

    public static void main(String[] args) {
    //final修饰变量,有且只能赋值一次
    //变量(变量包括局部变量和成员变量(成员变量包括类变量和实例变量))

        //final修饰局部变量用途1
        final int a;
        a = 1;
        //a = 2;  //final修饰的变量不能二次赋值

        final double pai = 3.14;

        final int[] arr = {1,2,3,4};
        //arr = null; //报错,变量arr不能二次赋值(变量arr存放的是数组{1,2,3,4}的地址)
        arr[0] = 5;  //final修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的

        //用final修饰成员变量-类变量,二次赋值报错
        //SCHOOL_NAME = "北大";  //二次赋值 报错

        //用final修饰成员变量-实例变量,二次赋值报错
        //demo d = new demo();
        //d.name = "李四";   //二次赋值 报错
    }

    //final修饰局部变量用途2
    public static void buy(final double z){
        //z = 1; //形参加了final,z不能被二次赋值(第一次赋值是传参的时候)
    }
}

final class A{}  //被final修饰后,类不能被继承
//class B extends A{}

class C{
    public final void test(){  //被final修饰后,方法不能被重写
        System.out.println("C test");
    }
}

class D extends C{
//    @Override
//    public void test(){
//        System.out.println("D test");
//    }
}

2.4.1 常量

        使用了static final修饰的成员变量就被称为常量

        作用:通常用于记录系统的配置信息

        命名规范:建议使用大写英文单词,多个单词使用下划线链接起来

使用常量记录系统配置信息的优势、执行原理:

        · 代码可读性更好,可维护性也更好

        · 程序编译后,常量会被“宏替换”;出现常量的地方全部会被替换成其记住的字面量,这样可以保证使用常量和直接用字面量的性能是一样的

public class demo {
    //常量:static final修饰的类变量是常量。常量建议名称全部大写,多个单词下划线连接
    public static final String SCHOOL_NAME = "清华";

    public static void main(String[] args) {
        System.out.println(SCHOOL_NAME);
        System.out.println(SCHOOL_NAME);
    }
}

2.5 抽象类

什么是抽象类?

        在Java中有一个关键字叫abstract,它就是抽象的意思,可以用它修饰类、成员方法

        abstract修饰类,这个类就是抽象类;修饰方法,这个方法就是抽象方法

修饰符 abstract class 类名{

        修饰符 abstract 返回值类型 方法名称(形参列表);

}

public abstract class A{

        public abstract void test();

}

 抽象类的注意事项、特点

· 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类

· 类该有的成员(成员变量、方法、构造器)抽象类都可以有

· 抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现

· 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类

//A 抽象类
public abstract class A {
    private String name;
    public static String schoolName;
    //抽象方法
    public abstract void run(); //抽象方法不能有方法体({})

    public A() {
    }

    public A(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static String getSchoolName() {
        return schoolName;
    }

    public static void setSchoolName(String schoolName) {
        A.schoolName = schoolName;
    }
}

//B 子类
//一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类
public class B extends A{
    @Override
    public void run() {

    }
}

//demo
public class demo {
    public static void main(String[] args) {
        //抽象类不能创建对象
        //A a = new A();  报错
    }
}

使用抽象类的好处

         父类知道每个子类都要做某个行为,但每个子类要做的情况不一样,父类就把该行为定义成抽象方法,交给子类去重写实现(这样设计是为了更好的支持多态

//Animal
public abstract class Animal {
    private String name;
    //抽象方法
    public abstract void action();

    public Animal() {
    }

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//Dog
public class Dog extends Animal {
    public Dog() {
    }

    public Dog(String name) {
        super(name);
    }

    @Override
    public void action() {
        System.out.println(getName()+"汪汪汪的叫……");
    }
}

//Cat
public class Cat extends Animal{
    @Override
    public void action() {
        System.out.println(getName()+"喵喵喵的叫……");
    }
}

//demo
public class demo {
    public static void main(String[] args) {
        Animal animal1 = new Dog("汪汪1号");
        animal1.action();

        Animal animal2 = new Cat();
        animal2.setName("喵喵1号");
        animal2.action();
    }
}

抽象类的常见应用场景:模板方法设计模式

        模板方法设计模式解决了方法中存在重复代码的问题

模板方法设计模式的写法

        1.定义一个抽象类

        2.在里面定义2个方法

                一个是模板方法:把相同代码放里面去

                一个是抽象方法:具体实现子类完成 

建议使用final关键字修饰模板方法

        模板方法是给对象直接使用的,不能被子类重写

        一旦子类重写了模板方法,模板方法就失效了

//Paper
public abstract class Paper {
    //模板方法
    //建议使用final关键字修饰模板方法(模板方法是给对象直接使用的,不能被子类重写)
    public final void write(){
        System.out.println("《我的爸爸》");
        System.out.println("我的爸爸今年40岁了!");
        zhengwen();
        System.out.println("对我而言他就是我的hero");
    }

    //抽象方法
    public abstract void zhengwen();
}

//Student
public class Student extends Paper{
    @Override
    public void zhengwen() {
        System.out.println("他个字很高……");
    }
}

//Teacher
public class Teacher extends Paper {
    @Override
    public void zhengwen() {
        System.out.println("他教会我骑车……");
    }
}

//demo
public class demo {
    public static void main(String[] args) {
        //模板方法设计模式
        //场景:学生、老师都要写一篇作文(我的爸爸),开头结尾都给定,正文自由发挥
        Paper paper1 = new Student();
        paper1.write();

        System.out.println("===================");

        Paper paper2 = new Teacher();
        paper2.write();
    }
}

2.6 接口

        Java提供了一个关键字interface,用这个关键字我们可以定义出一个特殊的结构:接口

public interface 接口名{

        //成员变量(常量)

        // 成员方法(抽象方法)

}

注意:接口不能创建对象;接口是用来被类实现(implements)的,是实现接口的类称为实现类

修饰符 class 实现类 implements  接口1,接口2,接口3,…{

}

 一个类可以实现多个接口(继承是亲爹,接口是干爹),实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类

//A
public interface A {
    //成员变量(常量)
    //前面加不加public static final 都是常量
    String SCHOOL_NAME = "清华";

    //成员方法(抽象方法)
    //前面加不加public abstract 都是抽象方法
    void testa();

    //接口中只能定义成员变量和成员方法,不能有构造器、不能有代码块
}

//B
public interface B {
    void testb1();
    void testb2();
}

//C
public interface C {
    void testc1();
    void testc2();
}

//D  实现类实现多个接口,必须重写完全部接口的全部抽象方法
public class D implements B,C {

    @Override
    public void testb1() {

    }

    @Override
    public void testb2() {

    }

    @Override
    public void testc1() {

    }

    @Override
    public void testc2() {

    }
}

//demo
public class demo {
    public static void main(String[] args) {
        System.out.println(A.SCHOOL_NAME);
        // A a = new A(); 接口不能创建对象

        D d = new D();  //D是实现类
    }
}

接口的好处 

        弥补了类单继承的不足,一个类同时可以实现多个接口

        让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现

//demo
public class demo {
    public static void main(String[] args) {
        Student s = new A();
        Driver d1 = new A();
        d1.drive();  //面向接口编程
        Singer si = new A();
        si.sing();

        Driver d2 = new B(); //面向接口编程
        d2.drive();  //面向接口编程

    }
}

class B implements Driver{

    @Override
    public void drive() {

    }
}

//A是Student的子类,也是Driver, Singer的实现类
class A extends Student implements Driver, Singer{

    @Override
    public void drive() {

    }

    @Override
    public void sing() {

    }
}

class Student{

}

interface Driver{
    void drive();

}

interface Singer{
    void sing();
}

接口的综合案例

//Student
public class Student {
    private String name;  //姓名
    private String sex;  //性别
    private double score;  //成绩

    public Student() {
    }

    public Student(String name, String sex, double score) {
        this.name = name;
        this.sex = sex;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}

//StudentOperator
//学生操作接口
public interface StudentOperator {
    public void printInfo(ArrayList<Student> arr);
    public void printAvgScore(ArrayList<Student> arr);
}

//StudentOperatorImpl1
//学生操作实现类  业务方案1
public class StudentOperatorImpl1 implements StudentOperator {
    @Override
    public void printInfo(ArrayList<Student> arr) {
        System.out.println("全班学生信息");
        System.out.println("姓名 性别 成绩");
        for (int i = 0; i < arr.size(); i++) {
            System.out.println(arr.get(i).getName()+" "+arr.get(i).getSex()+" "+arr.get(i).getScore());
        }
    }

    @Override
    public void printAvgScore(ArrayList<Student> arr) {
        double sum = 0;
        for (int i = 0; i < arr.size(); i++) {
            sum += arr.get(i).getScore();
        }
        double avg = sum / arr.size();
        System.out.println("全班学生的平均成绩是:"+avg);
    }
}

//StudentOperatorImpl2
//学生操作实现类  业务方案2
public class StudentOperatorImpl2 implements StudentOperator {
    @Override
    public void printInfo(ArrayList<Student> arr) {
        int boy = 0;   //男生人数
        int girl = 0;  //女生人数
        System.out.println("全班学生信息");
        System.out.println("姓名 性别 成绩");
        for (int i = 0; i < arr.size(); i++) {
            if(arr.get(i).getSex() == "男"){
                boy++;
            }else if(arr.get(i).getSex() == "女"){
                girl++;
            }
            System.out.println(arr.get(i).getName()+" "+arr.get(i).getSex()+" "+arr.get(i).getScore());
        }
        System.out.println("=====================");
        System.out.println("该班级男生"+boy+"人,女生"+girl+"人");
    }

    @Override
    public void printAvgScore(ArrayList<Student> arr) {
        double sum = 0;
        double max =arr.get(0).getScore();    //最高分
        double min =arr.get(0).getScore();    //最低分
        for (int i = 0; i < arr.size(); i++) {
            if(i != 0){
                if(arr.get(i).getScore()>max){
                    max = arr.get(i).getScore();
                }
                if(arr.get(i).getScore()<min){
                    min = arr.get(i).getScore();
                }
            }
            sum += arr.get(i).getScore();
        }
        double avg = (sum-min-max) / (arr.size()-2);
        System.out.println("全班学生的平均成绩是:"+avg);
    }
}

//ClassManage 班级管理类
public class ClassManage {
    //如果用数组,数组大小不可以变,这里更应该选用集合ArrayList(集合大小可变)
//  private Student[] s_arr = new Student[4];

    private ArrayList<Student> arr = new ArrayList<>();

    //如果要切换方案1  直接把StudentOperatorImpl2()改成StudentOperatorImpl1()
    private StudentOperator so2 = new StudentOperatorImpl2(); //面向接口编程

    public ClassManage() {
//        s_arr[0] = new Student("张三","男",99);
//        s_arr[1] = new Student("李四","男",89);
//        s_arr[2] = new Student("王五","男",59);
//        s_arr[3] = new Student("张三","男",99);

        arr.add(new Student("张三","男",70));
        arr.add(new Student("李四","男",89));
        arr.add(new Student("王五","男",59));
        arr.add(new Student("小美","女",99));
    }

    //打印全班学生信息
    public void printInfo(){
//        System.out.println("全班学生信息");
//        System.out.println("姓名 性别 成绩");
//        for (int i = 0; i < arr.size(); i++) {
//            System.out.println(arr.get(i).getName()+" "+arr.get(i).getSex()+" "+arr.get(i).getScore());
//        }
        so2.printInfo(arr);
    }

    //打印全班学生的平均成绩
    public void printAvgScore(){
//        double sum = 0;
//        for (int i = 0; i < arr.size(); i++) {
//            sum += arr.get(i).getScore();
//        }
//        double avg = sum / arr.size();
//        System.out.println("全班学生的平均成绩是:"+avg);
        so2.printAvgScore(arr);
    }

    //数组版本测试
//    public void method(){
//        for (int i = 0; i < s_arr.length; i++) {
//            System.out.println(s_arr[i].getName()+" "+s_arr[i].getSex()+" "+s_arr[i].getScore());
//        }
//    }
}

//demo
public class demo {
    public static void main(String[] args) {
        ClassManage cm = new ClassManage();
        cm.printInfo();
        System.out.println("=================");
        cm.printAvgScore();
    }
}

 接口的细节知识:JDK8开始,接口中新增的三种方法

        JDK8之前,接口中只能有成员变量(成员变量是常量)和成员方法(成员方法是抽象方法),JDK8开始,接口中新增三种方法

//A
public interface A {
    //新增 1.默认方法:必须使用default修饰,默认会被public修饰
    //默认方法是实例方法,对象的方法,因为接口不能创建对象,所以必须使用实现类的对象来访问
    //public可省略  default void test1(){}
    public default void test1(){
        //可以带方法体
        System.out.println("JDK8开始接口新增");
        System.out.println("默认方法:必须使用default修饰,默认会被public修饰");
        System.out.println("默认方法可以带方法体");

        test2(); //私有方法
    }

    //新增 2.私有方法:必须使用private修饰(JDK 9开始才支持)
    //私有方法也是实例方法,对象的方法,因为接是私有方法,所以实现类的对象也不能访问,接口内部方法可以访问
    private void test2(){
        System.out.println("JDK9开始接口新增");
        System.out.println("私有方法:必须使用private修饰");
    }

    //新增 3.类方法(静态方法):必须使用static修饰
    //默认会被public修饰,public可省略
    //类方法用类名直接调用,在接口里用接口名直接调用
    public static void test3(){
        System.out.println("JDK8开始接口新增");
        System.out.println("类方法(静态方法):必须使用static修饰");
    }
}

//B
public class B implements A{
}

//demo
public class demo {
    public static void main(String[] args) {
        //默认方法必须使用实现类的对象来访问
        B b = new B();
        b.test1();
        //b.test2(); //私有方法 实现类的对象不能访问,接口内部方法可以访问
        A.test3();
    }
}

接口的细节知识:接口的多继承、使用接口的注意事项

接口的多继承

        一个接口可以同时继承多个接口(作用是便于实现类去实现)

public interface C extends B,A{

}

public class demo {
    public static void main(String[] args) {

    }
}


interface A{
    void test1();
}

interface B{
    void test2();
}

//接口的多继承
interface C extends A , B{

}

//class D implements A , B{} 两者等同(实现C相当于实现了A和B)
//其实就是把AB省略直接写成C
class D implements C{
    @Override
    public void test1() {
        
    }

    @Override
    public void test2() {

    }
}

 接口的其他注意事项

1.一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承

2.一个类实现多个接口,如果多个接口中存在抽象方法签名冲突(不管返回值类型是否一致,方法名一致就冲突),则此时不支持多实现

3.一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的

4.一个类实现了多个接口,多个接口中存在同名的默认方法(返回值类型要一致,方法名也要一致),可以不冲突,这类重写该方法即可

//demo
public class demo {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.run();  //一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的
    }
}

interface A{
    void test1();  //抽象方法
}
interface B{
    String test1();  //抽象方法
}
//1.一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承
//interface C extends A,B{
//   冲突
//}

//2.一个类实现多个接口,如果多个接口中存在抽象方法签名冲突(不管返回值一不一样,方法名一样就冲突),则此时不支持多实现
//class C implements A,B{
//    冲突
//}

//3.一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的
class Fu{
    public void run(){
        System.out.println("父类的run方法");
    }
}

interface J{
    //public可省略
    public default void run(){
        System.out.println("接口的run方法");
    }
}

class Zi extends Fu implements J{

}

//4.一个类实现了多个接口,多个接口中存在同名的默认方法(返回值类型要一致,方法名也要一致),可以不冲突,这类重写该方法即可
interface A1{
    default void test1(){

    }  //默认方法
}
interface B1{
    default void test1(){

    }  //默认方法
}

class D implements A1,B1{
    //重写方法
    @Override
    public void test1() {

    }
}

2.7 内部类

        内部类是类中的五大成分之一(成员变量、方法、构造器、内部类、代码块),如果一个类定义在另一个类的内部,这个类就是内部类

        场景:当一个类的内部,包含一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类

public class Car{

        //内部类

        public class Engine{

        }

}

        内部类有四种形式:成员内部类、静态内部类、局部内部类、匿名内部类 

2.7.1 成员内部类

        就是类中的一个普通成员,类似前面学过的普通的成员变量、成员方法

public class Outer{

        //成员内部类

        public class Inner{

        }

}

注意:JDK16之前,成员内部类中不能定义静态成员,JDK16开始也可以定义静态成员

创建对象的格式:

        外部类名.内部类名 对象名 = new 外部类(…).new 内部类(…);

        Outer.Inner in = new Outer().new Inner();

//Outer
public class Outer {
    private int age = 99;
    public static String a;

    //成员内部类
    public class Inner{
        private String name;
        //private static String schoolName; //JDK16开始才支持定义静态成员
        public int age = 88;

        public void test(){
            System.out.println(age);
            System.out.println(a);

            int age = 66;

            System.out.println(age);  //66 就近原则
            System.out.println(this.age);  //88 成员内部类Inner的成员变量age的值
            System.out.println(Outer.this.age);  //99 指定访问外部类Outer的成员变量age的值

        }

        public Inner() {
        }

        public Inner(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

//demo
public class demo {
    public static void main(String[] args) {
        //创建成员内部类对象,需要先创建外部对象
        Outer.Inner inner = new Outer().new Inner();
        inner.setName("zhang");

        inner.test();
    }
}

2.7.2 静态内部类

        有static修饰的内部类,属于外部类自己持有

public class Outer{

        //静态内部类

        public static class Inner{

        }

}

创建对象的格式:

        外部类名.内部类名 对象名 = new 外部类.内部类(…);

        Outer.Inner in = new Outer.Inner();

静态内部类中访问外部类成员的特点

        可以直接访问外部类的静态成员,不可以直接访问外部类的实例成员

//Outer
public class Outer {
    private int age;
    public static String a;

    //静态内部类
    public static class Inner{
        private String name; //普通成员变量
        private static String schoolName;   //类变量(静态成员变量)

        public void test(){
            //类方法(静态方法)中可以直接访问类成员,不可以直接访问实例成员(实例成员属于对象)
            System.out.println(a);  //静态内部类可以访问外部类的类变量
            //System.out.println(age);  //报错  静态内部类不能访问外部类的实例变量
        }

        public Inner() {
        }

        public Inner(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

//demo
public class demo {
    public static void main(String[] args) {
        //创建静态内部类对象
        Outer.Inner inner = new Outer.Inner();
        inner.setName("zhang");
    }
}

2.7.3 局部内部类

        局部内部类是定义在方法中、代码块中、构造器等执行体中

 

2.7.4 匿名内部类

        匿名内部类就是一种特殊的局部内部类;所谓匿名:指的是程序员不需要为这个类声明名字

new 类或接口(参数值…){

        类体(一般是方法重写);

};

特点:匿名内部类本质就是一个子类,并会立即创建出一个子类对象

作用:用于更方便的创建一个子类对象

//demo
public class demo {
    public static void main(String[] args) {
//        Animal a = new Cat();
//        a.cry();

        //匿名内部类
        //1.把这个匿名内部类编译成子类,然后会立即创建出一个子类对象来
        Animal a = new Animal(){
            @Override
            public void cry() {
                System.out.println("喵喵喵");
            }
        };
        a.cry();
    }
}

//抽象类
abstract class Animal{
    public abstract void cry();
}

//class Cat extends Animal{
//    @Override
//    public void cry() {
//        System.out.println("喵喵喵");
//    }
//}

匿名内部类在开发中的使用场景

        通常作为一个参数传输给方法

//demo
public class demo {
    public static void main(String[] args) {
//        Swimming s1 = new Swimming(){
//            @Override
//            public void swim() {
//                System.out.println("小狗游泳");
//            }
//        };
//        go(s1);

        go(new Swimming(){
            @Override
            public void swim() {
                System.out.println("小狗游泳");
            }
        });

        Swimming s2 = new Swimming(){
            @Override
            public void swim() {
                System.out.println("小猫游泳");
            }
        };
        go(s2);
    }

    //设计一个方法,可以接收swimming接口的一切实现类对象进来参加游泳比赛
    public static void go(Swimming s){
        System.out.println("开始——————————————————");
        s.swim();
    }

}

//猫和狗都要参加游泳比赛
interface Swimming{
    void swim();
}

匿名内部类不是自己主动去用,是别人需要让我们用的时候用(别人需要的对象是接口类型用匿名内部类 或写实现类)

//demo
public class demo {
    public static void main(String[] args) {
        //搞清楚匿名内部类在开发中的真是使用场景
        //GUI编程(桌面程序)
        //1.创建窗口
        JFrame win = new JFrame("登录界面");
        JPanel panel = new JPanel();
        win.add(panel);

        JButton btn = new JButton("登录");
        panel.add(btn);   //窗口加按钮

        //给按钮绑定单机事件监听器
        //匿名内部类不是自己主动去用,是别人需要让我们用的时候用(别人需要的对象是接口类型用匿名内部类 或写实现类)
//        btn.addActionListener(new ActionListener() {
//            @Override
//            public void actionPerformed(ActionEvent e) {
//                JOptionPane.showMessageDialog(win,"登录一下");
//            }
//        });
        //匿名内部类的核心目的是简化代码
        btn.addActionListener(e -> JOptionPane.showMessageDialog(win,"登录一下"));

        win.setSize(400,400);  //窗口大小
        win.setLocationRelativeTo(null);  //窗口居中
        win.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);  //关闭窗口退出程序
        win.setVisible(true);  //可见


    }
}

2.8 枚举

        枚举是一种特殊类

枚举类的格式:

        修饰符 enum 枚举类名{

                名称1,名称2,…;

                其他成员…

}

注意:

        · 枚举类中的第一行,只能写一些合法的标识符(名称),多个名称用逗号隔开

        · 这些名称,本质是常量,每个常量都会记住枚举类的一个对象

//A
public enum A {
    //注意:枚举类的第一行必须罗列的是枚举对象的名字
    X, Y, Z;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//B
//抽象枚举
public enum B {
    //枚举类中的第一行,只能写一些合法的标识符(名称),这些名称本质是常量,每个常量都会记住枚举类的一个对象
    //因为枚举类中有抽象方法,所以这个枚举类是抽象枚举类,因此一个类继承抽象类,必须重写完抽象类的全部抽象方法
    X(){  //相当于创建了一个B枚举对象
        @Override
        public void go(){

        }
    },Y("张三"){  //相当于创建了一个B枚举对象
        @Override
        public void go(){
            System.out.println(getName());
        }
    };

    private String name;
    //private可省略,默认私有
    private B() {
    }
    //private可省略,默认私有
    private B(String name) {
        this.name = name;
    }

    public abstract void go();

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

//demo
public class demo {
    public static void main(String[] args) {
        A a1 = A.X;
        System.out.println(a1);

        //枚举类的构造器是私有的,不能对外创建对象
        //A a = new A();

        //枚举类的第一行都是常量,记住的是枚举类的对象
        A a2 = A.Y;

        //枚举类提供一些额外的API
        A[] all = A.values();  //拿到全部对象
        A a3 = A.valueOf("Z");
        System.out.println(a3.name());  //拿到a3的名字
        System.out.println(a3.ordinal());   //拿到a3的索引

        B b1 = B.Y;
        b1.go();
    }
}

用枚举类写单例设计模式

public enum C {
    X;  //单例
}

枚举的常见应用场景

        用来表示一组信息,然后作为参数进行传输

选择定义一个一个的常量来表示一组信息,并作为参数传输

        · 参数值不受约束

选择定义枚举表示一组信息,并作为参数传输

        · 代码可读性好,参数值得到了约束,对使用者更友好(建议!)

// Constant2
public enum Constant2 {
    BOY,GRIL;
}

//demo
public class demo {

    public static void main(String[] args) {
        //拿枚举类做信息标志和分类
        check(Constant2.BOY);
    }

    public static void check(Constant2 sex){
        switch (sex){
            case BOY:
                System.out.println("男生关注的信息");
                break;
            case GRIL:
                System.out.println("女生关注的信息");
                break;
        }
    }
}

2.9 泛型

        定义类、接口、方法时,同时声明了一个或者多个类型变量(如:<E>),称为泛型类、泛型接口、泛型方法,它们统称为泛型

public class ArrayList<E>{

        ……

}

作用:泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!这样可以避免强制类型转换,及其可能出现的异常

泛型的本质:把具体的数据类型作为参数传给类型变量

//demo
public class demo {
    public static void main(String[] args) {
        //没有定义泛型,默认时object类型
        ArrayList list = new ArrayList();

        list.add("java1");
        list.add("java2");
        list.add("java3");
        //list.add(new Cat());

        for (int i = 0; i < list.size(); i++) {
            String s = (String) list.get(i);
            System.out.println(s);
        }

        System.out.println("=====================");
        //ArrayList<String> list1 = new ArrayList<String>();
        ArrayList<String> list1 = new ArrayList<>(); //JDK1.7开始后面<>中可以不写类型
        list1.add("java1");
        list1.add("java2");
        list1.add("java3");
        //list1.add(new Cat());  报错(泛型可以在编译阶段约束能够操作的数据类型)

        for (int i = 0; i < list.size(); i++) {
            String s =list1.get(i);
            System.out.println(s);
        }
    }
}

2.9.1 泛型类

        自定义泛型类

修饰符 class 类名<类型变量,类型变量,……>{

}

//demo
public class demo {
    public static void main(String[] args) {
        MyArrayList<String> list = new MyArrayList<>();
        list.add("java1");
        list.add("java2");
        //list.add("java3");
        System.out.println(list.get(6));

        //泛型可以声明多个类型变量
        MyClass<String,String> c = new MyClass<>();

        //是它本身或者它的子类
        MyClass2<Dog> m1 = new MyClass2<>();
        MyClass2<Animal> m2 = new MyClass2<>();
    }
}

//MyArrayList
public class MyArrayList<E> {
    private Object[] o_arr = new Object[2];
    private int size=0;

    public boolean add(E e){
        if(size<o_arr.length){
            o_arr[size] = e;
            size++;
            return true;
        }else{
            System.out.println("超出数组长度");
            return false;
        }
    }

    public E get(int index){
        if(index<o_arr.length){
            return (E)o_arr[index];
        }else{
            return (E)"您访问的索引越界";
        }
    }
}

//MyClass(泛型可以声明多个类型变量)
public class MyClass<E,T> {
    public void put(E e,T t){

    }
}

//MyClass2  //是它本身或者它的子类
public class MyClass2<E extends Animal> {
}

//Animal
public class Animal {
}

//public class Dog extends Animal {
}

2.9.2 泛型接口

修饰符 interface 接口名<类型变量,类型变量,……>{

}

//demo
public class demo {
    public static void main(String[] args) {
        //系统需要处理学生和老师的数据
        //两个功能:保存对象数据,根据名称查询数据
        Teacher t = new Teacher("张三","男","Java");
        TeacherData t1 = new TeacherData();
        t1.add(t);
        t1.find("张三");
        t1.find("李四");
    }
}

//People
public class People {
    private String name;   //姓名
    private String sex;    //性别

    public People() {
    }

    public People(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

//Teacher
public class Teacher extends People{
    private String teach;  //学科

    public Teacher(){

    }

    public Teacher(String teach) {
        this.teach = teach;
    }

    public Teacher(String name, String sex, String teach) {
        super(name, sex);
        this.teach = teach;
    }

    public String getTeach() {
        return teach;
    }

    public void setTeach(String teach) {
        this.teach = teach;
    }
}

//Student
public class Student extends People{
    private int classroom;   //班级

    public Student(){

    }

    public Student(int classroom) {
        this.classroom = classroom;
    }

    public Student(String name, String sex, int classroom) {
        super(name, sex);
        this.classroom = classroom;
    }

    public int getClassroom() {
        return classroom;
    }

    public void setClassroom(int classroom) {
        this.classroom = classroom;
    }
}

//Data
//泛型接口
public interface Data<E> {
    void add(E e);  //即需要保存学生数据也需要保存老师数据,使用泛型比较好

    ArrayList<E> find(String name);
}

//TeacherData
public class TeacherData implements Data<Teacher> {
    private ArrayList<Teacher> arr_s = new ArrayList<>();  //保存老师数据的集合

    @Override
    public void add(Teacher teacher) {
        arr_s.add(teacher);
    }

    @Override
    public ArrayList<Teacher> find(String name) {
        for (int i = 0; i < arr_s.size(); i++) {
            if(arr_s.get(i).getName() == name){ //找到名字一样的
                System.out.println("查询成功");
                System.out.println(arr_s.get(i).getName()+" "+arr_s.get(i).getSex()+" "+arr_s.get(i).getTeach());
            }else{
                System.out.println("查询失败,老师列表中没有此人");
            }
        }
        return null;  //返回ArrayList<Teacher>感觉不太对,查询信息返回整个集合?? 应该返回学生对象即可
    }
}

//StudentData
public class StudentData implements Data<Student> {
    private ArrayList<Student> arr_s = new ArrayList<>();  //保存学生数据的集合

    @Override
    public void add(Student student) {
        arr_s.add(student);
    }

    @Override
    public ArrayList<Student> find(String name) {
        return null;
    }
}

2.9.3 泛型方法

修饰符<类型变量,类型变量,……>返回值类型 方法名(形参列表){

}

 通配符

        就是 ? 可以在使用泛型的时候代表一切类型;E T K V等字符是在定义泛型的时候使用

泛型的上下限

        泛型上限:?extends Car:?能接收的必须是Car或者它的子类

        泛型下限:?super Car:?能接收的必须是Car或者它的父类

//demo
public class demo {
    public static void main(String[] args) {
        String s = method("Java");
        System.out.println(s);

        Dog d = method(new Dog());
        System.out.println(d);

        //需求:所有汽车参加比赛
        ArrayList<Car> cars = new ArrayList<>();
        cars.add(new BENZ());
        cars.add(new BNW());
        go(cars);

        ArrayList<BNW> bnws = new ArrayList<>();
        bnws.add(new BNW());
        bnws.add(new BNW());
        //go(bnws);
        //报错 因为go的形参需要ArrayList<Car>,所以ArrayList<BNW>不行
        //虽然Car是BNW的父类,但是ArrayList<BNW>和ArrayList<Car>无关
        //public static void go(ArrayList<Car> cars)
        //如果想要把ArrayList<BNW>可以使用,需要把go方法加上泛型
        go(bnws);

    }

//    public static void go(ArrayList<Car> cars){
//
//    }

//    public static <T extends Car> void go(ArrayList<T> cars) {
//        //不加extends Car,任何ArrayList<XX>都能加进来
//    }
    //? 通配符  在使用泛型的时候可以代表一切类型  ? extends Car(上限 Car和Car的子类能进来)   ? super Car(下限 Car和Car的父类能进来)
    public static void go(ArrayList<? extends Car> cars) {
        //不加extends Car,任何ArrayList<XX>都能加进来
    }

    //泛型方法
    public static <T> T method(T t){
        return t;
    }
}

//Car
public class Car {
}

//BENZ
public class BENZ extends Car{
}

//BNW
public class BNW extends Car{
}

//Dog
public class Dog {
}

泛型的擦除问题和注意事项

        · 泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除

        · 泛型不支持基本数据类型(int、double等),只能支持对象类型(引用数据类型)

  • 29
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值