Java基础(2) 之面向对象

Java基础(2) 之面向对象

  • 面向对象编程有什么好处?

  • 面向对象的开发更符合人类的思维习惯,让编程变得更加简单、更加直观。

1.对象
  • 对象实质上是一种特殊的数据结构。这种结构怎么理解呢?

    你可以把对象理解成一张表格,表当中记录的数据,就是对象拥有的数据

    一句话总结,对象其实就是一张数据表,表当中记录什么数据,对象就处理什么数据。

  • Student s1 = new Student();这句话中的原理如下

    • Student s1表示的是在栈内存中,创建了一个Student类型的变量,变量名为s1

    • new Student()会在堆内存中创建一个对象,而对象中包含学生的属性名和属性值

      同时系统会为这个Student对象分配一个地址值0x4f3f5b24

    • 接着把对象的地址赋值给栈内存中的变量s1,通过s1记录的地址就可以找到这个对象

    • 当执行s1.name=“播妞”时,其实就是通过s1找到对象的地址,再通过对象找到对象的name属性,再给对象的name属性赋值为播妞;

2.类

用什么来设计这张表呢?就是类(class),类可以理解成对象的设计图,或者对象的模板。设计图中规定有哪些数据,对象中就只能有哪些数据。

  • 对象可以理解成一张数据表,而数据表中可以有哪些数据,是有类来设计的。
类的注意事项

在这里插入图片描述

3.this关键字
  • 哪一个对象调用方法,方法中的this就是哪一个对象

通过this在方法中可以访问本类对象的成员变量

4.构造器

构造器就是用来创建对象的。可以在创建对象时给对象的属性做一些初始化操作

  • 构造器其实是一种特殊的方法,但是这个方法没有返回值类型,方法名必须和类名相同。

  • 在创建对象时,会调用构造器。

  • 也就是说 new Student()就是在执行构造器,当构造器执行完了,也就意味着对象创建成功。

  • new 对象就是在执行构造方法

注意
  • 1.在设计一个类时,如果不写构造器,Java会自动生成一个无参数构造器。
  • 2.一旦定义了有参数构造器,Java就不再提供空参数构造器,此时建议自己加一个无参数构造器。
5.封装性

所谓封装,就是用类设计对象处理某一个事物的数据时,应该把要处理的数据,以及处理数据的方法,都设计到一个对象中去。

  • 封装的设计规范:

    • 合理隐藏、合理暴露
  • 体现:

    • private修饰的变量或者方法,只能在本类中被访问。

    • private double score; 就相当于把score变量封装在了Student对象的内部,且不对外暴露,你想要在其他类中访问score这个变量就,就不能直接访问了;

      如果你想给Student对象的score属性赋值,得调用对外暴露的方法setScore(int score),在这个方法中可以对调用者传递过来的数据进行一些控制,更加安全。

      在这里插入图片描述

6.实体JavaBean
实体类
  • 实体类中除了有给对象存、取值的方法就没有提供其他方法了。所以实体类仅仅只是用来封装数据用的。

实体类就是一种特殊的类,它需要满足下面的要求:

在这里插入图片描述

  • 实体类仅仅只用来封装数据,而对数据的处理交给其他类来完成,以实现数据和数据业务处理相分离
7.成员变量和局部变量的区别

在这里插入图片描述

面向对象的核心点就是封装,将数据和数据的处理方式,都封装到对象中; 至于对象要封装哪些数据?对数据进行怎样的处理? 需要通过类来设计。

需要注意的是,不同的人,对同一个对象进行设计,对象封装那些数据,提供哪些方法,可能会有所不同;只要能够完成需求,符合设计规范,都是合理的设计。

8.static

static读作静态,可以用来修饰成员变量,也能修饰成员方法。

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

Java中的成员变量按照有无static修饰分为两种:类变量、实例变量。它们的区别如下:

在这里插入图片描述

静态变量是属于类的,只需要通过类名就可以调用:类名.静态变量

实例变量是属于对象的,需要通过对象才能调用:对象.实例变量

static修饰成员方法

成员方法根据有无static也分为两类:类方法、实例方法

在这里插入图片描述

有static修饰的方法,是属于类的,称为类方法;调用时直接用类名调用即可。

无static修饰的方法,是属于对象的,称为实例方法;调用时,需要使用对象调用。

static的注意事项

在这里插入图片描述

工具类

如果一个类中的方法全都是静态的,那么这个类中的方法就全都可以被类名直接调用,由于调用起来非常方便,就像一个工具

  • 工具类里的方法全都是静态的,推荐用类名调用为了防止使用者用对象调用。我们可以把工具类的构造方法私有化
public class MyUtils{
    //私有化构造方法:这样别人就不能使用构造方法new对象了
    private MyUtils(){
        
    }
    
    //类方法
    public static String createCode(int n){
       ...
    }
}
  • 示例:生成验证码的工具类

  • public class MyUtils{
        public static String createCode(int n){
            //1.定义一个字符串,用来记录产生的验证码
            String code = "";
            
            //2.验证码是由所有的大写字母、小写字母或者数字字符组成
            //这里先把所有的字符写成一个字符串,一会从字符串中随机找字符
            String data = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKMNOPQRSTUVWXYZ";
            
            //3.循环n次,产生n个索引,再通过索引获取字符
            Random r = new Random();
            for(int i=0; i<n; i++){
                int index = r.nextInt(data.length());
                char ch = data.charAt(index);
                //4.把获取到的字符,拼接到code验证码字符串上。
                code+=ch;
            }
            
            //最后返回code,code的值就是验证码
            return code;
        }
    }
    
单例设计模式

让类对外只能产生一个对象;如任务管理器对象

  • 把类的构造器私有。

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

  • 定义一个类方法,返回对象。

  • 在这里插入图片描述

  • 懒汉式(拿对象时,才开始创建对象。)

    • 把类的构造器私有。

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

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

      在这里插入图片描述

9.代码块

代码块根据有无static修饰分为两种:静态代码块实例代码块

静态代码块

在这里插入图片描述

静态代码块不需要创建对象就能够执行

静态代码块,随着类的加载而执行,而且只执行一次

实例代码块
  • 实例代码块的作用和构造器的作用是一样的,用来给对象初始化值;而且每次创建对象之前都会先执行实例代码块.

    在这里插入图片描述

10.继承

在这里插入图片描述

子类对象实际上是由子、父类两张设计图共同创建出来的。

其实像这种两个类中有相同代码时,没必要重复写。

我们可以把重复的代码提取出来,作为父类,然后让其他类继承父类就可以了,这样可以提高代码的复用性。

权限修饰符

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

在这里插入图片描述

单继承与object
  • Java语言只支持单继承,不支持多继承,但是可以多层继承

  • Java是单继承的:一个类只能继承一个直接父类;

  • Object类是Java中所有类的祖宗

方法重写

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

  • 重写后,方法的访问遵循就近原则

    • 1.重写的方法上面,可以加一个注解@Override,用于标注这个方法是复写的父类方法

    • 2.子类复写父类方法时,访问权限必须大于或者等于父类方法的权限

      • public > protected > 缺省

      1. 重写的方法返回值类型,必须与被重写的方法返回值类型一样,或者范围更小
      1. 私有方法、静态方法不能被重写,如果重写会报错。
  • 示例:

    • public class A {
          public void print1(){
              System.out.println("111");
          }
      
          public void print2(int a, int b){
              System.out.println("111111");
          }
      }
      
    • public class B extends A{
          // 方法重写
          @Override // 安全,可读性好
          public void print1(){
              System.out.println("666");
          }
      
      
          // 方法重写
          @Override
          public void print2(int a, int b){
              System.out.println("666666");
          }
      }
      
    • 尽量做到声明不变,重新实现

应用场景之一就是:子类重写Object的toString()方法,以便返回对象的内容。

子类访问成员的特点

继承至少涉及到两个类,而每一个类中都可能有各自的成员(成员变量、成员方法),就有可能出现子类和父类有相同成员的情况,那么在子类中访问其他成员有什么特点呢?

  • 在子类中访问其他成员(成员变量、成员方法),是依据就近原则的

  • 如果子类和父类出现同名变量或者方法,优先使用子类的;此时如果一定要在子类中使用父类的成员,可以加this或者super进行区分。

子类访问构造器的特点

子类中访问构造器的语法规则

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

在这里插入图片描述

  • 如果不想使用默认的super()方式调用父类构造器,还可以手动使用super(参数)调用父类有参数构造器。

在这里插入图片描述

访问自己类的构造器

this(): 调用本类的空参数构造器
this(参数): 调用本类有参数的构造器

如下图:

在这里插入图片描述

  • 注意:this和super访问构造方法,只能用到构造方法的第一句,否则会报错。

访问本类成员:
this.成员变量 //访问本类成员变量
this.成员方法 //调用本类成员方法
this() //调用本类空参数构造器
this(参数) //调用本类有参数构造器

访问父类成员:
super.成员变量 //访问父类成员变量
super.成员方法 //调用父类成员方法
super() //调用父类空参数构造器
super(参数) //调用父类有参数构造器

11.多态

什么是多态?

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

在这里插入图片描述

在多态形式下,右边的代码是解耦合的,更便于扩展和维护。

定义方法时,使用父类类型作为形参,可以接收一切子类对象,扩展行更强,更便利。

public class Test2 {
    public static void main(String[] args) {
        // 目标:掌握使用多态的好处
		Teacher t = new Teacher();
		go(t);

        Student s = new Student();
        go(s);
    }

    //参数People p既可以接收Student对象,也能接收Teacher对象。
    public static void go(People p){
        System.out.println("开始------------------------");
        p.run();
        System.out.println("结束------------------------");
    }
}
多态的类型转换

多态形式下,不能调用子类特有的方法,比如在Teacher类中多了一个teach方法,在Student类中多了一个study方法,这两个方法在多态形式下是不能直接调用的。

在这里插入图片描述

  • 但是转型后是可以调用的

在这里插入图片描述

  • 因此我们最终记住一句话:原本是什么类型,才能还原成什么类型
12.final关键字

面向对象编程中偶尔会用到的一个关键字叫final,也是为后面学习抽象类接口做准备的

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

    • 在这里插入图片描述

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

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

    • 在这里插入图片描述

      在这里插入图片描述

      在这里插入图片描述

  • static final 修饰的成员变量,称之为常量

  • 在程序编译后,常量会“宏替换”,出现常量的地方,全都会被替换为其记住的字面量

13.抽象类

在Java中有一个关键字叫abstract,它就是抽象的意思,它可以修饰也可以修饰方法。

  • 被abstract修饰的类,就是抽象类
  • 被abstract修饰的方法,就是抽象方法(不允许有方法体
//abstract修饰类,这个类就是抽象类
public abstract class A{
    //abstract修饰方法,这个方法就是抽象方法
    public abstract void test();
}
  • 类的成员(成员变量、成员方法、构造器),类的成员都可以有。
  • 抽象类是不能创建对象的,如果抽象类的对象就会报错

抽象类虽然不能创建对象,但是它可以作为父类让子类继承。而且子类继承父类必须重写父类的所有抽象方法。

子类继承父类如果不复写父类的抽象方法,要想不出错,这个子类也必须是抽象类

  • 好处

    • 1.用抽象类可以把父类中相同的代码,包括方法声明都抽取到父类,这样能更好的支持多态,一提高代码的灵活性。

      2.反过来用,我们不知道系统未来具体的业务实现时,我们可以先定义抽象类,将来让子类去实现,以方便系统的扩展。

模板方法模式

模板方法模式解决了多个子类中有相同代码的问题

第1步:定义一个抽象类,把子类中相同的代码写成一个模板方法。
第2步:把模板方法中不能确定的代码写成抽象方法,并在模板方法中调用。
第3步:子类继承抽象类,只需要父类抽象方法就可以了。

  • 模板方法模式主要解决方法中存在重复代码的问题

  • 在这里插入图片描述

  • 我们可以写一个抽象类C类,在C类中写一个doSing()的抽象方法。再写一个sing()方法

    先设计模板方法:

    // 模板方法设计模式
    public abstract class C {
        // 模板方法
        public final void sing(){
            System.out.println("唱一首你喜欢的歌:");
    
            doSing();
    
            System.out.println("唱完了!");
        }
    
        public abstract void doSing();
    }
    
  • 写一个A类和B类继承C类,复写doSing()方法,代码如下

    • public class A extends C{
          @Override
          public void doSing() {
              System.out.println("我是一只小小小小鸟,想要飞就能飞的高~~~");
          }
      }
      
      public class B extends C{
          @Override
          public void doSing() {
              System.out.println("我们一起学猫叫,喵喵喵喵喵喵喵~~");
          }
      }
      
14.接口
  • 总结为一句话,就是接口实现之后我就可以用接口来接收所有实现了接口的对象,因为所有的这些对象都重写了接口的方法,充分利用了多态。

比抽象类抽象得更加彻底的一种特殊结构

  • Java提供了一个关键字interface,用这个关键字来定义接口这种特殊结构。格式如下:

  • public interface 接口名{
        //成员变量(常量)
        //成员方法(抽象方法)
    }
    

    定义好接口之后,是不能创建对象的

  • 所以说接口有什么用?

    • 接口是用来被类实现(implements)的,我们称之为实现类。
    • 一个类是可以实现多个接口的(接口可以理解成干爹),类实现接口必须重写所有接口的全部抽象方法,否则这个类也必须是抽象类
    • 比如,定义一个B接口,里面有两个方法testb1(),testb2()

    • public interface B {
          void testb1();
          void testb2();
      }
      
    • 再定义一个C接口,里面有两个方法testc1(), testc2()

    • public interface C {
          void testc1();
          void testc2();
      }
      
    • 再写一个实现类D,同时实现B接口和C接口,此时就需要复写四个方法

    • // 实现类
      public class D implements B, C{
          @Override
          public void testb1() {
      
          }
          @Override
          public void testb2() {
      
          }
          @Override
          public void testc1() {
      
          }
      
          @Override
          public void testc2() {
      
          }
      }
      
好处:
  • 弥补了类单继承的不足,一个类同时可以实现多个接口。

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

  • class Student{
    
    }
    
    interface Driver{
        void drive();
    }
    
    interface Singer{
        void sing();
    }
    
    //A类是Student的子类,同时也实现了Dirver接口和Singer接口
    class A extends Student implements Driver, Singer{
        @Override
        public void drive() {
    
        }
    
        @Override
        public void sing() {
    
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            //想唱歌的时候,A类对象就表现为Singer类型
            Singer s = new A();
            s.sing();
    		
            //想开车的时候,A类对象就表现为Driver类型
            Driver d = new A();
            d.drive();
        }
    }
    
  • 接口弥补了单继承的不足,同时可以轻松实现在多种业务场景之间的切换。

  • 在JDK8版本以后接口中能够定义的成员也做了一些更新

    • public interface A {
          /**
           * 1、默认方法:必须使用default修饰,默认会被public修饰
           * 实例方法:对象的方法,必须使用实现类的对象来访问。
           */
          default void test1(){
              System.out.println("===默认方法==");
              test2();
          }
      
          /**
           * 2、私有方法:必须使用private修饰。(JDK 9开始才支持的)
           *   实例方法:对象的方法。
           */
          private void test2(){
              System.out.println("===私有方法==");
          }
      
          /**
           * 3、静态方法:必须使用static修饰,默认会被public修饰
           */
           static void test3(){
              System.out.println("==静态方法==");
           }
      
           void test4();
           void test5();
           default void test6(){
      
           }
      }
      
      • 我们写一个B类,实现A接口。B类作为A接口的实现类,只需要重写抽象方法就尅了,对于默认方法不需要子类重写。
      public class B implements A{
          @Override
          public void test4() {
      
          }
      
          @Override
          public void test5() {
      
          }
      }
      
      public class Test {
          public static void main(String[] args) {
              // 目标:掌握接口新增的三种方法形式
              B b = new B();
              b.test1();	//默认方法使用对象调用
              // b.test2();	//A接口中的私有方法,B类调用不了
              A.test3();	//静态方法,使用接口名调用
          }
      }
      

      就是说jdk8以后接口中的方法可以被default,static,private修饰,这些方法可以在接口中实现,而什么都不加,就不能实现,得实体类来实现。感觉开始和抽象类差不多了。

接口也能继承
  • 一个接口可以继承多个接口,接口同时也可以被类实现。

  • 1.一个接口继承多个接口,如果多个接口中存在相同的方法声明,则此时不支持多继承
    2.一个类实现多个接口,如果多个接口中存在相同的方法声明,则此时不支持多实现
    3.一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会有限使用父类的方法
    4.一个类实现类多个接口,多个接口中有同名的默认方法,则这个类必须重写该方法。

public class Test {
    public static void main(String[] args) {
        // 目标:理解接口的多继承。
    }
}

interface A{
    void test1();
}
interface B{
    void test2();
}
interface C{}

//比如:D接口继承C、B、A
interface D extends C, B, A{

}

//E类在实现D接口时,必须重写D接口、以及其父类中的所有抽象方法。
class E implements D{
    @Override
    public void test1() {

    }

    @Override
    public void test2() {

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

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

成员内部类
  • 既可以访问内部类成员、也可以访问外部类成员
  • 如果内部类成员和外部类成员同名,可以使用**类名.this.成员**区分

成员内部类就是类中的一个普通成员,类似于成员变量、成员方法。

  • public class Outer {
       private int age = 99;
       public static String a="黑马";
    
       // 成员内部类
       public class Inner{
           private String name;
           private  int age = 88;
    
           //在内部类中既可以访问自己类的成员,也可以访问外部类的成员
           public void test(){
               System.out.println(age); //88
               System.out.println(a);   //黑马
    
               int age = 77;
               System.out.println(age); //77
               System.out.println(this.age); //88
               System.out.println(Outer.this.age); //99
           }
       }
    }
    

    成员内部类如何创建对象,格式如下:

    //外部类.内部类 变量名 = new 外部类().new 内部类();
    Outer.Inner in = new Outer().new Inner();
    //调用内部类的方法
    in.test();
    
静态内部类

在成员内部类的前面加了一个static关键字。静态内部类属于外部类自己持有。

  • public class Outer {
        private int age = 99;
        public static String schoolName="黑马";
    
        // 静态内部类
        public static class Inner{
            //静态内部类访问外部类的静态变量,是可以的;
            //静态内部类访问外部类的实例变量,是不行的
            public void test(){
                System.out.println(schoolName); //99
                //System.out.println(age);   //报错
            }
        }
    }
    

    静态内部类创建对象时,需要使用外部类的类名调用。

    //格式:外部类.内部类 变量名 = new 外部类.内部类();
    Outer.Inner in = new Outer.Inner();
    in.test();
    
局部内部类

局部内部类是定义在方法中的类,和局部变量一样,只能在方法中有效。所以局部内部类的局限性很强,一般在开发中是不会使用的

public class Outer{
    public void test(){
        //局部内部类
        class Inner{
            public void show(){
                System.out.println("Inner...show");
            }
        }
        
        //局部内部类只能在方法中创建对象,并使用
        Inner in = new Inner();
        in.show();
    }
}
匿名内部类

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

匿名内部类本质上是一个没有名字的子类对象、或者接口的实现类对象。

匿名内部类的作用:简化了创建子类对象、实现类对象的书写格式。

new 父类/接口(参数值){
    @Override
    重写父类/接口的方法;
}
  • 示例:

  • public abstract class Animal{
        public abstract void cry();
    }
    

    我想要在不定义子类的情况下创建Animal的子类对象,就可以使用匿名内部类

    public class Test{
        public static void main(String[] args){
            //这里后面new 的部分,其实就是一个Animal的子类对象
            //这里隐含的有多态的特性: Animal a = Animal子类对象;
            Animal a = new Animal(){
                @Override
                public void cry(){
                    System.out.println("猫喵喵喵的叫~~~");
                }
            }
            a.eat(); //直线上面重写的cry()方法
        }
    }
    
    • 匿名内部类在编写代码时没有名字,编译后系统会为自动为匿名内部类生产字节码,字节码的名称会以外部类$1.class的方法命名
  • **只有在调用方法时,当方法的形参是一个接口或者抽象类,为了简化代码书写,而直接传递匿名内部类对象给方法。**这样就可以少写一个类。

    • public interface Swimming{
          public void swim();
      }
      
      public class Test{
          public static void main(String[] args){
              Swimming s1 = new Swimming(){
                  public void swim(){
                      System.out.println("狗刨飞快");
                  }
              };
              go(s1);
              
              Swimming s1 = new Swimming(){
                  public void swim(){
                      System.out.println("猴子游泳也还行");
                  }
              };
              go(s1);
          }
          //形参是Swimming接口,实参可以接收任意Swimming接口的实现类对象
          public static void go(Swimming s){
              System.out.println("开始~~~~~~~~");
              s.swim();
              System.out.println("结束~~~~~~~~");
          }
      }
      
16.枚举

枚举是一种特殊的类,它的格式是:

public enum 枚举类名{
    枚举项1,枚举项2,枚举项3;
}

枚举项就表示枚举类的对象,只是这些对象在定义枚举类时就预先写好了,以后就只能用这几个固定的对象。

  • 枚举项实际上是枚举类的对象

  • 枚举类A是用class定义的,说明枚举确实是一个类,而且X,Y,Z都是A类的对象;而且每一个枚举项都是被public static final 修饰,所以被可以类名调用,而且不能更改。

在这里插入图片描述

  • 既然枚举是一个类的话,我们能不能在枚举类中定义构造器、成员变量、成员方法呢?答案是可以的

    • public enum A{
          //定义枚举项
          X,Y,Z("张三"); //枚举项后面加括号,就是在执行枚举类的带参数构造方法。
          
          //定义空构造器
          public A(){
              
          }
          
          //成员变量
          private String name;
          //定义带参数构造器
          public A(String name){
              this.name=name;
          }
          
          //成员方法
          public String getName(){
              return name;
          }
          ...
      }
      

      虽然枚举类中可以像类一样,写一些类的其他成员,但是一般不会这么写,如果你真要这么干的话,到不如直接写普通类来的直接。

  • 枚举一般表示几个固定的值,然后作为参数进行传输

public enum Constant{
    BOY,GRIL
}
public class Test{
    public static void main(String[] args){
        //调用方法,传递男生
        provideInfo(Constant.BOY);
    }
    
    public static void provideInfo(Constant c){
        switch(c){
            case BOY:
                System.out.println("展示一些信息给男生看");
                break;
            case GRIL:
                System.out.println("展示一些信息给女生看");
                break;
        }
    }
}
17.泛型
泛型类
  • 泛型类,在实际工作中一般都是源代码中写好,我们直接用的,就是ArrayList<E>这样的,自己定义泛型类是非常少的。

所谓泛型指的是,在定义类、接口、方法时,同时声明了一个或者多个类型变量(如:),称为泛型类、泛型接口、泛型方法、它们统称为泛型。

  • ArrayList类就是一个泛型类

  • ArrayList集合的设计者在定义ArrayList集合时,就已经明确ArrayList集合时给别人装数据用的,但是别人用ArrayList集合时候,装什么类型的数据他不知道,所以就用一个<E>表示元素的数据类型。

    当别人使用ArrayList集合创建对象时,new ArrayList<String> 就表示元素为String类型,new ArrayList<Integer>表示元素为Integer类型。

    • 泛型的好处:在编译阶段可以避免出现一些非法的数据。

    • 泛型的本质:把具体的数据类型传递给类型变量。

在这里插入图片描述

自定义泛型类
//这里的<T,W>其实指的就是类型变量,可以是一个,也可以是多个。
public class 类名<T,W>{
    
}
  • 示例:
//定义一个泛型类,用来表示一个容器
//容器中存储的数据,它的类型用<E>先代替用着,等调用者来确认<E>的具体类型。
public class MyArrayList<E>{
    private Object[] array = new Object[10];
    //定一个索引,方便对数组进行操作
    private int index;
    
    //添加元素
    public void add(E e){
        array[index]=e;
        index++;
    }
    
    //获取元素
    public E get(int index){
        return (E)array[index];
    }
}
自定义泛型接口
  • 在实际工作中,一般也都是框架底层源代码把泛型接口写好,我们实现泛型接口就可以了.

泛型接口其实指的是在接口中把不确定的数据类型用<类型变量>表示。定义格式如下:

//这里的类型变量,一般是一个字母,比如<E>
public interface 接口名<类型变量>{
    
}

示例:做一个系统要处理学生和老师的数据,需要提供2个功能,保存对象数据、根据名称查询数据,要求:这两个功能处理的数据既能是老师对象,也能是学生对象。

public class Teacher{

}
public class Student{
    
}

定义一个Data<T>泛型接口,T表示接口中要处理数据的类型。

public interface Data<T>{
    public void add(T t);
    
    public ArrayList<T> getByName(String name);
}
  • 这个接口可以让不同的类实现操作不同的数据

    • //此时确定Data<E>中的E为Teacher类型,
      //接口中add和getByName方法上的T也都会变成Teacher类型
      public class TeacherData implements Data<Teacher>{
         	public void add(Teacher t){
              
          }
          
          public ArrayList<Teacher> getByName(String name){
              
          }
      }
      
      //此时确定Data<E>中的E为Student类型,
      //接口中add和getByName方法上的T也都会变成Student类型
      public class StudentData implements Data<Student>{
         	public void add(Student t){
              
          }
          
          public ArrayList<Student> getByName(String name){
              
          }
      }
      
泛型方法
public <泛型变量,泛型变量> 返回值类型 方法名(形参列表){
    
}
  • 在返回值类型和修饰符之间有定义的才是泛型方法
  • 在这里插入图片描述
public class Test{
    public static void main(String[] args){
        //调用test方法,传递字符串数据,那么test方法的泛型就是String类型
        String rs = test("test");
    
        //调用test方法,传递Dog对象,那么test方法的泛型就是Dog类型
    	Dog d = test(new Dog()); 
    }
    
    //这是一个泛型方法<T>表示一个不确定的数据类型,由调用者确定
    public static <T> test(T t){
        return t;
    }
}
泛型限定

泛型限定的意思是对泛型的数据类型进行范围的限制。有如下的三种格式

  • <?> 表示任意类型
  • <? extends 数据类型> 表示指定类型或者指定类型的子类
  • <? super 数据类型> 表示指定类型或者指定类型的父类

假设有Car作为父类,BENZ,BWM两个类作为Car的子类

class Car{}
class BENZ extends Car{}
class BWN extends Car{}

public class Test{
    public static void main(String[] args){
        //1.集合中的元素不管是什么类型,test1方法都能接收
        ArrayList<BWM> list1 = new ArrayList<>();
        ArrayList<Benz> list2 = new ArrayList<>();
        ArrayList<String> list3 = new ArrayList<>();
        test1(list1);
        test1(list2);
        test1(list3);
        
        //2.集合中的元素只能是Car或者Car的子类类型,才能被test2方法接收
        ArrayList<Car> list4 = new ArrayList<>();
        ArrayList<BWM> list5 = new ArrayList<>();
        test2(list4);
        test2(list5);
        
        //2.集合中的元素只能是Car或者Car的父类类型,才能被test3方法接收
        ArrayList<Car> list6 = new ArrayList<>();
        ArrayList<Object> list7 = new ArrayList<>();
        test3(list6);
        test3(list7);
    }
    
    public static void test1(ArrayList<?> list){
        
    }
    
    public static void test2(ArrayList<? extends Car> list){
        
    }
    
   	public static void test3(ArrayList<? super Car> list){
        
    }
}
泛型擦除

就是说泛型只能编译阶段有效,一旦编译成字节码,字节码中是不包含泛型的而且泛型只支持引用数据类型,不支持基本数据类型

在这里插入图片描述

反编译后:ArrayList后面没有泛型

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值