Java Basics (1)—Simple use of generics

1. Definition and functions of generics

Generics are a programming mechanism that allows the use of parameterized types when writing code to achieve type safety at compile time. The following is the role of generics:

  1. Enhance code readability and maintainability : By using generic parameters in your code, you can make your code clearer, more readable, and maintainable.

  2. Improved code safety : Generics can check types at compile time, preventing type conversion errors at runtime.

  3. Increased code reusability : Generics allow writing common code on different data types, thereby increasing code reusability.

  4. Simplify the code : Using generics can simplify the code by avoiding writing similar code repeatedly.

In short, generics are a powerful programming mechanism that can help developers write code that is more readable, maintainable, safe, and reusable.

Java generics perform type erasure (Type Erasure) during compilation. Type erasure means that when the compiler compiles generic code, it will erase the generic type to its original type or qualified type, so that the generic type does not exist at runtime. When performing type erasure, the compiler replaces generic type parameters with their bounding (qualified) type (if any), or with the Object type if no qualifying type is specified.

For example, a generic class is defined as follows:

public class Box<T> {
    
    
    private T content;
    public void setContent(T content) {
    
    
        this.content = content;
    }
    public T getContent() {
    
    
        return content;
    }
}

After compilation, type erasure will erase the generic type T to the Object type, and the generated bytecode is as follows:

public class Box {
    
    
    private Object content;
    public void setContent(Object content) {
    
    
        this.content = content;
    }
    public Object getContent() {
    
    
        return content;
    }
}

It should be noted that although the generic type does not exist at runtime, the compiler still performs type checking on the generic type during compilation to ensure type safety.

2. Use of generics

1. Generics are defined on the class

code show as below:

class CommonUtil {
    
    

  public Object obj;

  public Object getObj() {
    
    
    return obj;
  }

  public void setObj(Object obj) {
    
    
    this.obj = obj;
  }
}

When using it, the code is as follows:

  public static void main(String[] args) {
    
    

    CommonUtil tool = new CommonUtil<>();
    
    tool.setObj(2);
    Integer val1 = (Integer)tool.getObj();
    
	tool.setObj("hello java");
    String val2 = (String)tool.getObj();
  }

As can be seen from the above code, forced conversion is required every time when obtaining the value. Forced conversion errors can easily occur if you are not careful. So is there a way to avoid type coercion? The answer is: generics. By defining generics on a class, you can avoid type conversion. The improved code is as follows:

class CommonUtil<T> {
    
    

  public T obj;

  public T getObj() {
    
    
    return obj;
  }

  public void setObj(T obj) {
    
    
    this.obj = obj;
  }
}

When creating an instance, pass in the specific type. The code is as follows:

  public static void main(String[] args) {
    
    

    CommonUtil<Integer> tool = new CommonUtil<>();
    tool.setObj(2);
    Integer val1 = tool.getObj();
    
    CommonUtil<String> tool = new CommonUtil<>();
	tool.setObj("hello java");
    String val2 = tool.getObj();
  }

2. Generic definition method

There is one not so good point about the generic definition on the class. Take a look at the following example. The code is as follows:

class CommonUtil<T> {
    
    

  public void show(T obj){
    
    
    System.out.println("obj = " + obj);
  }
}

The code usage code is as follows:

    CommonUtil<Integer> tool1 = new CommonUtil<>();
    tool1.show(value);

    CommonUtil<String> tool2 = new CommonUtil<>();
    tool2.show("111");
    
	CommonUtil<Person> tool2 = new CommonUtil<>();
    tool2.show(new Person());

Did you find that every time you call the show() method, you need to create an instance, because you can specify the specific parameter type when creating. But creating objects in this way is too troublesome. How to solve it? You can define generics on methods. The code is as follows:

class CommonUtil {
    
    

  public <W> void show(W obj){
    
    
    System.out.println("obj = " + obj);
  }
}

The usage code is as follows:

    CommonUtil tool1 = new CommonUtil<>();
    tool1.show(value);
    tool1.show("111");
    tool1.show(new Person());

Finally, the specific parameter type is passed in when the method is called, so that repeated creation of objects can be avoided and a method can be called repeatedly, which is very similar to Object. Static methods including static modification can also be used. code show as below:

class CommonUtil {
    
    

  public static <W> void show(W obj){
    
    
    System.out.println("obj = " + obj);
  }
}

The usage code is as follows:

    CommonUtil.show(value);
    CommonUtil.show("111");
    CommonUtil.show(new Person());

However, when defining generics on static methods, please note that this static method cannot use generics on the class. Why? Because the specific parameter type is specified when the class is created, and the static method is loaded into the JVM before the class is instantiated, there is no way to know what specific parameter type your class passed in when creating an instance. Examples of errors are as follows:

class CommonUtil<W> {
    
    

  public static <W> void show(W obj){
    
    
    System.out.println("obj = " + obj);
  }
}

3. Generics are defined on the interface

Sometimes generics are defined on interfaces, and the code is as follows:

public interface Inter<T> {
    
    

	public void print(T obj);
}

Generics on the interface can be specified in the subclass, or not specified. The specific type specified by the subclass is as follows:

public InterImpl implements Inter<String> {
    
    
	// 接口上的方法
	public void print(String obj){
    
    }
	// 子类独有方法
	protect void show(Object obj){
    
    }
}

When in use, the code is as follows:

	Inter<String> inter = new InterImpl();
	inter.print("hello");
	
	InterImpl impl = new InterImpl();
	impl.show(new Object());

Or define generics in subclasses and interfaces to define specific types. The code is as follows:

public InterImpl<W> implements Inter<String> {
    
    
	// 接口上的方法
	public void print(String obj){
    
    }
	// 子类独有方法
	protect void show(W obj){
    
    }
}

When in use, the code is as follows:

	Inter<String> inter = new InterImpl();
	inter.print("hello");
	
	InterImpl<Integer> impl = new InterImpl();
	impl.show(new Integer(10));

If the specific type is not known in the subclass, you can also define a generic and pass the subclass generic to the interface. The code is as follows:

public InterImpl<W> implements Inter<W> {
    
    
	// 接口上的方法
	public void print(W obj){
    
    }
	// 子类独有方法
	protect void show(W obj){
    
    }
}

When in use, the code is as follows:

	Inter<String> inter = new InterImpl();
	inter.print("hello");
	
	InterImpl<Integer> impl = new InterImpl();
	impl.show(new Integer(10));

4. Generic wildcards

For example, the code is as follows:

public class FanxinDemo {
    
    

  public static void print(Collection<String> list) {
    
    
    list.forEach(e->{
    
    
      System.out.println("e = " + e);
    });
  }

  public static void main(String[] args) {
    
    

    List<String> list1 = new ArrayList<>();
    list1.add("a");
    list1.add("b");
    list1.add("c");

    List<Integer> list2 = new ArrayList<>();
    list2.add(1);
    list2.add(2);

    print(list1);
 }
}

It can be found that the print() method can only output parameter types of String, but not other parameters. How can this code be shared? Here is a generic wildcard ?. When you don’t know what type a generic is, you can use this temporary representation. The code is as follows:

public class FanxinDemo {
    
    

  public static void print(Collection<?> list) {
    
    
    list.forEach(e->{
    
    
      System.out.println("e = " + e);
    });
  }

  public static void main(String[] args) {
    
    

    List<String> list1 = new ArrayList<>();
    list1.add("a");
    list1.add("b");
    list1.add("c");

    List<Integer> list2 = new ArrayList<>();
    list2.add(1);
    list2.add(2);

    print(list1);
    print(list2);
 }
}

But suppose you don't want the print() method to be used by Integer type parameters, what should you do? This requires generic qualification.

5. Generic limitation

Generic qualification can limit the parameter type range. ?This level range is too large and unsafe. The range is limited by the extends and super keywords. However, these two keywords can only be single inheritance, so they are also limited. There are two types of restrictions: making it available to all parent classes and making it available to all subclasses.

5.1. Make it available to all parent classes

code show as below:

  public static void print(Collection<? super Integer> list) {
    
    
    list.forEach(e->{
    
    
      System.out.println("e = " + e);
    });
  }

Indicates that all parent classes of Integer can use the print() method.

5.2. Make it available to all subclasses

code show as below:

  public static void print(Collection<? extends String>> list) {
    
    
    list.forEach(e->{
    
    
      System.out.println("e = " + e);
    });
  }

Indicates that all String subclasses can use the print() method.

Guess you like

Origin blog.csdn.net/qq_35971258/article/details/129031946
Recommended