创建对象的几种方式
- 使用 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运算符后不能使用泛型类。
个人微信公众号,欢迎关注及时获取技术干货!