Java必知基础(五)

创建对象的几种方式

  • 使用 Java的 new关键字

首先创建将要实例化的类


public class TestEntity {

    private String name;
    private String sex;
    private int age;

    public TestEntity() {
    }

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

    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 int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

在需要使用对象的使用new 关键字根据构造参数创建即可

   public void init(){
        TestEntity test1 = new TestEntity();
        TestEntity teset2 = new TestEntity("小明");

    }

  • 使用Class类的newInstance创建对象,用到的是反射技术,它只能调用目标对象的无参构造方法创建对象,如下
    public void init(){
//        TestEntity test1 = new TestEntity();
//        TestEntity teset2 = new TestEntity("小明");

        try {
            //需要传入全类名
            Class<?> aClass = Class.forName("com.li.test.TestEntity");
            TestEntity o = (TestEntity) aClass.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch  (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }
  • 使用Constructor类的newInstance方法和上面的有点像,可以通过newInstance方法调用有参的构造方法,甚至是private修饰的构造方法
    public void init(){
        try {
            Constructor<TestEntity> constructor = TestEntity.class.getConstructor(String.class);
            TestEntity test1 = constructor.newInstance("小明");

            Constructor<TestEntity> declaredConstructor = TestEntity.class.getDeclaredConstructor();
            declaredConstructor.setAccessible(true);
            TestEntity testEntity = declaredConstructor.newInstance();
            
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
  • 使用clone方法创建对象,JVM会将被克隆的对象的数据全部拷贝到新的对象中,该方法不会调用任何构造方法,只是在Heap中新开辟一块内存,将数据写进去;但是前提是得给Bean实现Cloneable接口并实现clone方法
  public void init(){
     TestEntity testEntity = new TestEntity();
        try {
            TestEntity testEntity1 = testEntity.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

    }
  • 使用反序列化一个对象时,JVM会给我们创建一个新的对象,但不会调用任何构造方法;要想反序列化一个对象,需要实现Serializable接口
    public void init(){
        TestEntity testEntity = new TestEntity();

        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(testEntity);
            objectOutputStream.close();

            ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
            TestEntity testEntity1 = (TestEntity) objectInputStream.readObject();
            objectInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

Java集合

Java集合框架提供了数据持有对象的方式,提供了对数据集合的操作。Java集合框架位于java.util包下,主要有三个大类:Collection、Map接口以及对集合进行操作的工具类。
如下图所示:
在这里插入图片描述
在这里插入图片描述

也可以分为Set、List、Queue和Map四种体系,ArrayList、HashSet、LinkedList、TreeSet、HashMap,TreeMap是我们经常会有用到的已实现的集合类。
其中Set代表无序、不可重复的集合;List代表有序、重复的集合;而Map则代表具有映射关系的集合。Java 5 又增加了Queue体系集合,代表一种队列集合实现。

Java集合和数组的区别:
  • 数组长度在初始化时指定,意味着只能保存定长的数据。而集合可以保存数量不确定的数据。同时可以保存具有映射关系的数据(即关联数组,键值对 key-value)。
  • 数组元素即可以是基本类型的值,也可以是对象。集合里只能保存对象(实际上只是保存对象的引用变量),基本数据类型的变量要转换成对应的包装类才能放入集合类中。
Iterator

Iterator接口经常被称作迭代器,它与Collection接口、Map接口是依赖关系。但Iterator主要用于遍历集合中的元素。 Iterator接口中主要定义了2个方法:

//如果仍有元素可以迭代  返回true
    boolean hasNext();
    // 返回下一个迭代元素
    E next();

如ArrayList的iterator()方法:

  public void init(){
        ArrayList list = new ArrayList();
        list.iterator();
    }
//其ArrayList中的实现为
  public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * An optimized version of AbstractList.Itr
     */
     //其内部类实现 Iterator
    private class Itr implements Iterator<E> 

泛型

为了解决元素存储的安全问题和获取元素时需要类型强转问题,从Java 5 增加了泛型以后,Java集合可以记住容器中对象的数据类型,使得编码更加简洁、健壮。
在泛型出现之前,是这么写代码的:

那么就会出现类型转换异常问题(ClassCastException)
当泛型出现之后就可以避免此类问题:
在这里插入图片描述
这就是泛型。泛型是对Java语言类型系统的一种扩展,有点类似于C++的模板,可以把类型参数看作是使用参数化类型时指定的类型的一个占位符。引入泛型,是对Java语言一个较大的功能增强,带来了很多的好处。比如:

  1. 类型安全。类型错误现在在编译期间就被捕获到了,而不是在运行时当作java.lang.ClassCastException展示出来,将类型检查从运行时挪到编译时有助于开发者更容易找到错误,并提高程序的可靠性。
  2. 消除了代码中许多的强制类型转换,增强了代码的可读性。
  3. 为较大的优化带来了可能。

泛型的使用

允许在定义接口、类、构造器等时声明类型形参,类型形参在整个接口、类体内可当成类型使用,几乎所有可使用普通类型的地方都可以使用这种类型形参。

ArrayList中的代码足以说明泛型是使用范围:

//定义接口时指定了一个类型形参,该形参名为E
public interface List<E> extends Collection<E> {
   //在该接口里,E可以作为类型使用
   public E get(int index) {}
   public void add(E e) {} 
}

//定义类时指定了一个类型形参,该形参名为E
public class ArrayList<E> extends AbstractList<E> implements List<E> {
   //在该类里,E可以作为类型使用
  public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
}

在JDK 1.7 增加了泛型的“菱形”语法:Java允许在构造器后不需要带完成的泛型信息,只要给出一对尖括号(<>)即可,Java可以推断尖括号里应该是什么泛型信息。
如下所示:

  public void init(){
                                      //未声明类型
        ArrayList<Integer> list = new ArrayList<>();
        list.add(12);
        list.add(13);
        for (int i = 0; i < list.size(); i++) {
            Integer o = (Integer) list.get(i);
            System.out.print(o);
        }
    }

**泛型类派生子类:**当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或者从该父类派生子类,需要注意:使用这些接口、父类派生子类时不能再包含类型形参,需要传入具体的类型。
错误的方式:

public class A extends Container<K, V>{}

正确的方式:

public class A extends Container<Integer, String>{}

也可以不指定具体的类型,如下:

public class A extends Container{}

**泛型方法:**就是在声明方法时定义一个或多个类型形参。 泛型方法的用法格式如下:

修饰符<T, S> 返回值类型 方法名(形参列表){
   方法体
}
方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。
   public <T> void sayHello(T t){
        System.out.print(t.getClass().getName());
    }

泛型构造器:和使用普通泛型方法一样没区别,一种是显式指定泛型参数,另一种是隐式推断,如果是显式指定则以显式指定的类型参数为准,如果传入的参数的类型和指定的类型实参不符,将会编译报错。

public class Child extends Base {

  
  public <S> Child(S s){
    System.out.print(s.getClass().getName());
  }
  
}

  public void init(){
        //隐式
        Child child = new Child(15);
        //显示
        Child child1 = new <String> Child("小明");
    }

类型通配符

类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是元素类型未知的List)。这个问号(?)被成为通配符,它的元素类型可以匹配任何类型。
不带限通配符:

  public void init(List<?> par){
        ArrayList arrayList = new ArrayList();
        arrayList.add(par);
    }
    可以添加任意类型的list 等同于下面的代码
      public void init(List par){
        ArrayList arrayList = new ArrayList();
        arrayList.add(par);
    }

带限通配符:使用通配符的目的是来限制泛型的类型参数的类型,使其满足某种条件,固定为某些类。

主要分为两类即:上限通配符下限通配符

  1. 上限通配符:如果想限制使用泛型类别时,只能用某个特定类型或者是其子类型才能实例化该类型时,可以在定义类型时,使用extends关键字指定这个类型必须是继承某个类,或者实现某个接口,也可以是这个类或接口本身。
它表示集合中的所有元素都是Base类型或者其子类
List<? extends Base>
  public void init(){
     ArrayList<? extends Base> arrayList = new ArrayList<Child>();
    }
    这样就确定集合中元素的类型,虽然不确定具体的类型,但最起码知道其父类。然后进行其他操作。

上边界通配符直接使用add()方法受限,但是可以用来获取各种数据类型的数据,并赋值给父类型的引用。

  public void init(){
     ArrayList<? extends Base> arrayList = new ArrayList<>();
     arrayList.add(new Base());//报错,因为list不能确定实例化的对象具体类型导致add()方法受限
     arrayList.add(new Child());报错,因为list不能确定实例化的对象具体类型导致add()方法受限
    }

正确的使用方法

   public void init(){
     ArrayList<Base> arrayList = new ArrayList<>();
     arrayList.add(new Base());
     arrayList.add(new Child());
     print(arrayList);

    }
    
    private void print(List<? extends Base> list) {// 通配符作形参
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));// 使用get方法
        }
    }
  1. 下限通配符:如果想限制使用泛型类别时,只能用某个特定类型或者是其父类型才能实例化该类型时,可以在定义类型时,使用super关键字指定这个类型必须是是某个类的父类,或者是某个接口的父接口,也可以是这个类或接口本身。
  public void init(){
     ArrayList<? super Base> arrayList = new ArrayList<>();
     arrayList.add(new Base());
     arrayList.add(new Child());

    }
    这就是所谓的下限通配符,使用关键字super来实现,实例化时,指定类型实参只能是extends后类型的子类或其本身。

下边界通配符使用get()方法受限(不使用强转),但是可以添加特定类型的值,用于将对象写入到一个数据结构里

  public void init(){
     ArrayList<? super Base> arrayList = new ArrayList<>();
     arrayList.add(new Base());
     arrayList.add(new Child());
     Base object = arrayList.get(0);//强转的情况下报错,因为List<? super Base>不知道list存放的对象具体类型,则使用get获取到的值不确定。
    }

正确用法

   public void init(){
   
        addInt(new ArrayList<Integer>());
        addFloat(new ArrayList<Float>());
    
    }

    private void addInt(List<? super Integer> list) {// 添加int
        list.add(1);
        list.add(2);
        list.add(3);
    }

    private void addFloat(List<? super Float> list) {// 添加 float
       list.add(1.1f);
       list.add(1.2f);
       list.add(1.3f);
    }
  • 限定通配符总是包括自己;
  • 上界类型通配符:add方法受限;
  • 下界类型通配符:get方法受限;
  • 如果你想从一个数据类型里获取数据,使用 ? extends 通配符;
  • 如果你想把对象写入一个数据结构里,使用 ? super 通配符;
  • 如果你既想存,又想取,那就别用通配符;
  • 不能同时声明泛型通配符上界和下界。

类型擦除

Class c1=new ArrayList<Integer>().getClass();
Class c2=new ArrayList<String>().getClass();
System.out.println(c1==c2);
输出结果为  true

这是因为不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一类处理,在内存中也只占用一块内存空间。从Java泛型这一概念提出的目的来看,其只是作用于代码编译阶段,在编译过程中,对于正确检验泛型结果后,会将泛型的相关信息擦出,也就是说,成功编译过后的class文件中是不包含任何泛型信息的。泛型信息不会进入到运行时阶段。

在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。


个人微信公众号,欢迎关注及时获取技术干货!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值