Java必知基础(五)

    xiaoxiao2022-07-04  176

    创建对象的几种方式

    使用 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语言一个较大的功能增强,带来了很多的好处。比如:

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

    泛型的使用

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

    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); }

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

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

    上限通配符:如果想限制使用泛型类别时,只能用某个特定类型或者是其子类型才能实例化该类型时,可以在定义类型时,使用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方法 } } 下限通配符:如果想限制使用泛型类别时,只能用某个特定类型或者是其父类型才能实例化该类型时,可以在定义类型时,使用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运算符后不能使用泛型类。


    个人微信公众号,欢迎关注及时获取技术干货!

    最新回复(0)