zoukankan      html  css  js  c++  java
  • java基础(9)

    1.类加载器

    类的加载:

    概述:当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类的初始化

    加载:

    • 就是指将class文件读入内存,并为之创建一个Class对象
    • 任何类被使用时系统都会建立一个Class对象

    连接:

    • 验证:是否有正确的内部结构,并和其他类协调一致
    • 准备:负责为类的静态成员分配内存,并设置默认初始值
    • 解析:将类的二进制数据中的符号引用替换为直接引用

    初始化:像默认初始化,显示初始化,构造初始化等

    类初始化的时机

    1.创建类的实例

    2.访问类的静态变量,或者为静态变量赋值

    3.调用类的静态方法

    4.使用反射方式来强调创建某个类或接口对应的java.lang.Class对象

    5.初始化某个类的子类

    6.直接使用java.exe命令来运行某个主类

    类加载器

    概述:类加载器负责将.class文件加载到内存中,并为之生成对应的Class对象,虽然我们不需要关系类加载器机制,但是了解这个机制我们就能更好的理解程序的运行

    类加载器的组成:

    • Bootstrap ClassLoader:根类加载器
      • 也被成为引导类加载器,负责java核心类的加载,比如System,String等,在JDK中JRE的lib目录下的rt.jar文件中
    • Extension ClassLoader:扩展类加载器
      • 负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下的ext目录
    • System ClassLoader:系统类加载器
      • 负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径

    2.反射

    概述:简单的说就是通过class文件对象(也就是Class类的对象),去使用该文件中的成员变量,构造方法和成员方法

    Class类

    获取class文件对象的三种方式

    1.类名.class

    2.对象.getClass()

    3.Class:forName("类名"):这里的类名是包括包名的全称类名(推荐使用)

    代码示例:

    import java.util.Date;
    
    public class Demo10 {
    	public static void main(String[] args) throws ClassNotFoundException {
    		String str = "123";
    		Class cls1 = str.getClass();
    		Class cls2 = String.class;
    		Class cls3 = Class.forName("java.lang.String");
    		
    		System.out.println(cls1 == cls2);//true
    		System.out.println(cls1 == cls3);//true
    		
    		System.out.println(cls1.isPrimitive());//false,判断是否为基本数据类型的Class实例对象的方法
    		System.out.println(int.class == Integer.class);//false
    		System.out.println(int.class == Integer.TYPE);//true,TYPE是基本数据类型包装类和void所独有的静态字段,代表着基本数据类型的字节码
    		int[] array = {1, 3, 4};
    		System.out.println(array.getClass().isPrimitive());//false
    		System.out.println(array.getClass().isArray());//true,判断Class是否为数组的方法
    	}
    }
    

    通过反射获取成员变量,构造方法,成员方法:

    1.获取成员变量: Field

    • public Field[] getFields():获取所有公共变量
    • public Field[] getDeclaredFields():获取所有变量
    • public Field getField(String name):获取单个指定的变量(不能为私有的变量)
    • public Field getDeclaredField(String name):获取单个变量(可以是私有变量)
    • 字段对象.set(对象, “该字段的值”):设置某个字段(成员变量)的值

    2.获取构造方法: Constructor

    • public Constructor[] getConstructors():获得所有的公共构造方法
    • public Constructor[] getDeclaredConstructors():获取所有的构造方法
    • public Constructor getConstructor(Class<?>... parameterTypes):获取单个公共的构造方法,参数表示的是你要获取的构造方法的构造参数个数及数据类型的class字节码文件对象
    • public Constructor getDeclaredConstructor(Class<?>... parameterTypes):获取单个构造方法(包括私有构造方法),参数表示的是你要获取的构造方法的构造参数个数及数据类型的class字节码文件对象
    • public T newInstance(Object... instargs):使用此Constructor对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例

    3.获取成员方法: Method

    • public Method[] getMethods():获取自己的包括父亲的成员方法
    • public Method[] getDeclaredMethods():获取自己的方法
    • public Method getMethod(String name, Class<?>... parameterTypes):获取单个方法(不可获取私有的方法)
    • public Method getDeclaredMethod(String name, Class<?>... parameterTypes):获取单个方法(可获得私有的方法)
    • public Object invoke(Object obj, Object... args):返回值是object接受,第一个参数表示对象是谁,第二个参数表示调用该方法的实际参数

    代码示例1(通过反射获取和设置成员变量):

    //cn.luyi.demo1.Person.java
    
    package cn.luyi.demo1;
    
    public class Person {
    	private String name;
    	private int age;
    	public String city;
    	public Person(){};
    	private Person(String name){
    		this.name = name;
    	};
    	public Person(String name, int age){
    		this.name = name;
    		this.age = age;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getAge() {
    		return age;
    	}
    	public void setAge(int age) {
    		this.age = age;
    	}
    	@Override
    	public String toString() {
    		return "Person [name=" + name + ", age=" + age + ", city=" + city + "]";
    	}
    	
    }
    //Demo1
    public class Demo1 {
    	public static void main(String[] args) throws Exception{
    		Class c = Class.forName("cn.luyi.demo1.Person");
    	
    		Constructor con = c.getConstructor();
    		Object obj = con.newInstance();
    		
    		//获取单个公共变量
    		Field cityField = c.getField("city");
    		cityField.set(obj, "广州");
    		System.out.println(obj);//Person [name=null, age=0, city=广州]
    		
    		//获取单个私有变量
    		Field nameField = c.getDeclaredField("name");
    		nameField.setAccessible(true);
    		nameField.set(obj, "卢一");
    		System.out.println(obj);//Person [name=卢一, age=0, city=广州]
    	}
    }
    

    代码示例2(通过反射获取构造方法):

    //cn.luyi.demo1.Person.java
    
    package cn.luyi.demo1;
    
    public class Person {
    	private String name;
    	private int age;
    	public String city;
    	public Person(){};
    	private Person(String name){
    		this.name = name;
    	};
    	public Person(String name, int age){
    		this.name = name;
    		this.age = age;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getAge() {
    		return age;
    	}
    	public void setAge(int age) {
    		this.age = age;
    	}
    	@Override
    	public String toString() {
    		return "Person [name=" + name + ", age=" + age + ", city=" + city + "]";
    	}
    	
    }
    
    //Demo2.java
    
    import java.lang.reflect.Constructor;
    
    public class Demo2 {
    	public static void main(String[] args) throws  Exception {
    		Class c = Class.forName("cn.luyi.demo1.Person");
    		
    		//获得无参数构造方法
    		Constructor con = c.getConstructor();
    		//通过这个无参构造创建一个新实例
    		Object obj = con.newInstance();
    		System.out.println(obj);//Person [name=null, age=0, city=null]
    		
    		//获得带参数的构造方法
    		Constructor con2 = c.getConstructor(String.class, int.class);
    		//通过这个有参构造创建一个新实例
    		Object obj2 = con2.newInstance("luyi", 19);
    		System.out.println(obj2);//Person [name=luyi, age=19, city=null]
    		
    		//获得带参数的私有构造方法
    		Constructor con3 = c.getDeclaredConstructor(String.class);
    		//设置反射的对象在使用的时候取消java语言访问检查,不设置会抛出IllegalAccessExcetion异常
    		con3.setAccessible(true);
    		//通过这个私有带参构造方法获得一个新实例
    		Object obj3 = con3.newInstance("huangyi");
    		System.out.println(obj3);//Person [name=huangyi, age=0, city=null]
    	}
    }
    

    代码案例3(通过反射获取成员方法):

    //Student.java
    public class Student {
    	public Student(){
    		
    	};
    	public void show(){
    		System.out.println("I am a student");
    	}
    	private int sum(int n){
    		int plus = 0;
    		for(int i = 0; i <= n; i ++){
    			plus += i;
    		}
    		return plus;
    	}
    }
    
    //Demo3.java
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    
    public class Demo3 {
    	public static void main(String[] args) throws Exception {
    		Class c = Class.forName("cn.luyi.demo1.Student");
    		
    		Constructor con = c.getConstructor();
    		Object obj = con.newInstance();
    		//获取无参数的成员方法
    		Method method1 = c.getMethod("show");
    		method1.invoke(obj);
    		
    		//获取私有带参的成员方法
    		Method method2 = c.getDeclaredMethod("sum", int.class);
    		method2.setAccessible(true);
    		//获得返回值
    		Integer result = (Integer)method2.invoke(obj, 10);
    		System.out.println(result);
    	}
    }
    

    反射案例

    案例1:通过反射越过泛型检查

    import java.lang.reflect.Method;
    import java.util.ArrayList;
    
    /*
     * 怎么给一个ArrayList<Integer>对象添加一个字符串数据呢?
     */
    public class Demo4 {
    	public static void main(String[] args) throws Exception {
    		ArrayList<Integer> array = new ArrayList<Integer>();
    		
    		Class c = array.getClass();
    		//ArrayList的字节码文件传的这个参数的类型为Object类型,而不是Integer类型
    		Method m = c.getMethod("add", Object.class);
    		m.invoke(array, "Hello");
    		
    		System.out.println(array);
    	}
    }
    

    3.动态代理

    代理概述:本来应该自己做的事情,却请了别人来做,被请的人就是代理对象

    动态代理概述:在程序运行过程中产生的这个动态代理对象,而我们的动态代理其实就是通过反射来生产一个代理的

    动态代理的实现:在java中的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandle接口,通过使用这个类和接口就可以生成动态代理对象,JDK提供的代理只能针对接口来做代理,我们有更强大的代理cglib可以不仅仅针对接口来做

    Proxy类中的方法创建动态代理对象:

    • public static Object newProxyInstance(ClassLoader loader,Class<?>[] interface,InvocationHandler h),最终会调用InvocationHandler的方法
    • InvocationHandler接口重写的方法:Object invoke(Object proxy, Method method,Object[] args)

    具体代码实现(通过动态代理给增删改查方法添加权限功能和日志记录功能):

    //UserDao.java
    
    public interface UserDao {
    	abstract public void add();
    	
    	abstract public void delete();
    	
    	abstract public void update();
    
    	abstract public void find();
    }
    
    
    //UserDaoImpl.java
    
    public class UserDaoImpl implements UserDao {
    
    	@Override
    	public void add() {
    		System.out.println("添加功能");
    	}
    
    	@Override
    	public void delete() {
    		System.out.println("删除功能");
    	}
    
    	@Override
    	public void update() {
    		System.out.println("修改功能");
    	}
    
    	@Override
    	public void find() {
    		System.out.println("查找功能");
    
    	}
    
    }
    
    //MyInvocationHandle.java
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class MyInvocationHandle implements InvocationHandler {
    	private Object target;//目标对象
    	
    	public MyInvocationHandle(Object target){
    		this.target = target;
    	}
    	@Override
    	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    		System.out.println("权限校验");
    		Object result = method.invoke(target, args);
    		System.out.println("日志记录");
    		return result;
    	}
    
    }
    
    //Text.java
    
    import java.lang.reflect.Proxy;
    
    public class Test {
    	public static void main(String[] args) {
    		UserDao ud = new UserDaoImpl();
    		//对ud对象做一个代理
    		MyInvocationHandle handle = new MyInvocationHandle(ud);
    		UserDao proxy = (UserDao)Proxy.newProxyInstance(ud.getClass().getClassLoader(), ud.getClass().getInterfaces(),handle);
    		
    		proxy.add();
    		proxy.delete();
    		proxy.update();
    		proxy.find();
    	}
    }
    

    4.枚举(Enum)

    为什么要有枚举

    1.问题:要定义星期几或性别的变量,该怎么定义呢?假设用1-7分别表示星期一到星期天,但有人可能就会把星期天用0表示

    2.枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错,枚举可以让编译器在编译时就控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标

    枚举类的注意事项

    1.定义枚举类要用关键字enum

    2.所有枚举类都是Enum的子类

    3.枚举类的第一行必须是枚举项,最后一个枚举项后面的分号是可以省略的,但是如果枚举类有其他的东西,这个枚举类不能省略

    4.枚举类可以有构造器,但必须是private修饰的,它默认也是private修饰的

    5.枚举类可以有抽象方法,但是枚举项必须将重写该方法

    6.枚举可以用在switch语句中

    枚举的基本应用

    定义:public enum 枚举名称{枚举元素列表}

    使用:

    public class Demo8 {
    	public static void main(String[] args) {
    		//使用枚举
    		WeekDay weekDay = WeekDay.Fri;
    		System.out.println(weekDay);//Fri
    		//枚举对象的成员方法
    		System.out.println(weekDay.name());//Fri
    		System.out.println(weekDay.ordinal());//5,在枚举的内容中是第几个(从0开始)
    		//枚举类的静态方法
    		System.out.println(WeekDay.valueOf("Sun"));//Sun,把字符串Sun变成对应的枚举元素
    		System.out.println(WeekDay.values().length);//7,得到了有所有枚举元素的数组,然后求这个数组的长度
    	}
    	//定义了一个枚举类,列举出星期天到星期六
    	public enum WeekDay{
    		Sun, Mon, Tue, Wed, Thi, Fri, Sat
    	}
    }
    

    实现带有构造方法的枚举

    public enum WeekDay{
    	//如果想要使用带有参数的构造方法,则需要在各个枚举元素后面加个小括号然后传参
    	Sun() , Mon(1), Tue("abc"), Wed, Thi, Fri, Sat;
    	
    	//构造方法必须放在枚举元素列表之后,而且方法必须私有
    	private WeekDay(){
    		System.out.println("执行了无参数的构造方法");
    	};
    	private WeekDay(int day){
    		System.out.println("执行了带有整数参数的构造方法");
    	};
    	private WeekDay(String day){
    		System.out.println("执行了带有字符串类型参数的构造方法");
    	}
    }
    

    实现带有抽象方法的枚举

    public class Demo9 {
    	public static void main(String[] args) {
    		TrafficeLamp t = TrafficeLamp.RED;
    		System.out.println(t);//RED
    		System.out.println(t.nextLamp());//GREEN
    		System.out.println(t.getTime());//30
    		System.out.println(t.nextLamp().nextLamp().getTime());//3
    	}
    	
    	//实现了带有抽象方法以及带有参数的构造方法的枚举
    	public enum TrafficeLamp{
    		//这里的每个枚举元素后面跟个参数,就相当于new一个对象后面跟个参数的构造方法
    		//后面加个大括号调用抽象方法,就相当于前面new出来的对象调用了这个抽象方法
    		RED(30){
    			@Override
    			public TrafficeLamp nextLamp(){
    				return GREEN;
    			}
    		}, 
    		GREEN(30){
    			@Override
    			public TrafficeLamp nextLamp(){
    				return YELLOW;
    			}
    		}, 
    		YELLOW(3){
    			@Override
    			public TrafficeLamp nextLamp(){
    				return RED;
    			}
    		};
    	
    		public abstract TrafficeLamp nextLamp();
    		private int time;
    		private TrafficeLamp(int time){
    			this.time = time;
    		}
    		public int getTime() {
    			return time;
    		}
    		public void setTime(int time) {
    			this.time = time;
    		}
    		
    	}
    }
    

    枚举实现单例模式

    保证整个系统中的一个类只有一个对象的实例,实现这种功能的方式就是单例模式

    当枚举只有一个成员时,就可以作为一种单例的实现方式了

    5.方法引用的基本入门(简化Lambda表达式,java8之后才有)

    在某些场景之下,Lambda表达式要做的事情,其实在另外一个类里已经写过了,那么此时如果通过Lambda表达式重复编写相同的代码,就是浪费,那么,如何才能复用已经存在的方法逻辑呢?

    接下来我们来看一个案例:

    //Cook.java
    public class Cook {
    	
    	//定义了一个做菜的静态方法
    	public static void makeFood(String food){
    		System.out.println("将" + food + "作成可口的食物");
    	}
    }
    
    //Sitter.java
    //定义一个保姆接口
    public interface Sitter {
    	void work(String food);
    }
    
    //Demo3.java
    public class Demo3 {
    	public static void main(String[] args) {
    		//直接使用Lambda表达式
    		hireSitter(food -> System.out.println("将" + food + "作成可口的食物"));
    		//使用方法引用,引用了在Cook类里的makeFood方法
    		hireSitter(Cook::makeFood);
    		
    	}
    	
    	//雇佣一个保姆,并且让她去做菜
    	public static void hireSitter(Sitter sitter){
    		sitter.work("白菜");
    	}
    }
    

    通过以上这个例子,我们也可以大致了解到方法引用的格式有一个为:类名::这个类里的方法

    接下来我们再具体看看方法引用的两种常见格式

    通过类名称引用静态方法的格式:类名称::静态方法

    通过对象名引用成员方法的格式:对象名::成员方法

    6.Stream流式思想(java8之后才有)

    stream流式操作可以帮我们节省很多的代码,接下来我们就来认识一下stream流吧

    java8当中的“流”其实就是Stream接口的对象

    JDK提供了一个流接口:java.util.stream.Stream

    如何获取流?

    1.根据集合获取流:集合名称.stream()

    2.根据数组获取流:Stream.of(数组名称)

    代码示例:

    public class Demo1 {
    	public static void main(String[] args) {
    		ArrayList<String> list = new ArrayList<>();
    		list.add("luyi");
    		list.add("luer");
    		list.add("lusan");
    		
    		Stream<String> streamA = list.stream();
    		
    		String[] array = {"lusi", "luwu", "luliu"};
    		
    		Stream<String> streamB = Stream.of(array);
    	}
    }
    

    Stream流的map映射方法

    获取流之后,可以使用映射方法:map(用于转换的Lambda表达式)

    映射:就是将一个对象转换成为另一个对象,把老对象映射到新对象上

    使用映射把字符串类型的数据转换为数字类型的数据的示例:

    import java.util.ArrayList;
    import java.util.stream.Stream;
    
    public class Demo2 {
    	public static void main(String[] args) {
    		ArrayList<String> list = new ArrayList<>();
    		list.add("100");
    		list.add("200");
    		list.add("300");
    		
    		//与list.stream().map(Integer::parseInt)等价
    		/*Stream<Integer> stream = list.stream().map((String str) -> {
    			return Integer.parseInt(str);
    		});*/
    		
    		Stream<Integer> stream = list.stream().map(Integer::parseInt);
    		
    		System.out.println(stream);
    	}
    }
    

    Stream流的filter过滤方法

    如果希望对流当中的元素进行过滤,可以使用过滤方法

    格式:filter(能产生boolean结构的Lambda)

    过滤掉不是luyi的人的示例

    public class Demo3 {
    	public static void main(String[] args) {
    		ArrayList<String> list = new ArrayList<>();
    		list.add("luyi");
    		list.add("luer");
    		list.add("lusan");
    		
    		Stream<String> streamA = list.stream().filter((String str) ->{
    			boolean b = "luyi".equals(str);
    			return b;
    		});//filter那部分代码等价于filter(str -> "luyi".equlas(str));
    		
    	}
    }
    

    Stream流的forEach遍历方法

    如果希望在流当中进行元素的遍历操作,可以使用forEach方法

    forEach(Lambda表达式):意思是,对流当中的每一个元素都要进行操作。参数Lambda表达式必须是一个能够消费一个参数,而且不会产生数据结果的Lambda

    例如:

    Lambda s->System.out.println(s);等价于

    方法引用:System.out::println

    forEach的使用:

    import java.util.stream.Stream;
    
    public class Demo4 {
    	public static void main(String[] args) {
    		String[] array = {"赵露思" , "赵丽颖", "赵四"};
    		//遍历Stream流打印数据
    		Stream.of(array).forEach((String str)->{
    			System.out.println(str);
    		});
    		//等价于forEach(s->System.out.println(s));
    		//也等价于forEach(System.out::println);
    	}
    }
    

    Stream流几个方法的综合使用:

    import java.util.ArrayList;
    import java.util.stream.Stream;
    
    public class Demo4 {
    	public static void main(String[] args) {
    		ArrayList<String> list = new ArrayList<>();
    		list.add("赵露思,20");
    		list.add("赵丽颖,30");
    		list.add("赵四,40");
    		//过滤掉岁数小于20的人
    		list.stream().map(s -> s.split(",")[1]).map(s -> Integer.parseInt(s)).filter(n -> n > 20).forEach(System.out::println);
    	}
    }
    

    并发的Stream流:支持并发操作的流

    流当中的元素如果特别多,那么只有一个人在逐一、挨个处理,肯定比较慢,费劲

    如果对流当中的元素,使用多个人同时处理,这就是“并发”

    那么如何才能获取“并发流”呢?

    格式:.parallelStream()

    注意事项:

    1.使用并发流操作的时候,到底有几个人进行同时操作呢?不用管,JDK自己处理

    2.只要正确使用,就不会出现多个人抢到同一个元素的情况

    3.如果已经获取了一个普通的流,那么只要再调用一下parallel()方法也会变成并发流

    示例代码:

    import java.util.ArrayList;
    
    public class Demo5 {
    	public static void main(String[] args) {
    		ArrayList<String> list = new ArrayList<>();
    		for(int i = 1; i <= 100; i ++){
    			list.add("hello----" + i);
    		}
    		
    		//这是只有一个人在做打印输出的操作
    		list.stream().forEach(System.out::println);
    		//这是多个人在做打印输出的操作
    		list.parallelStream().forEach(System.out::println);
    		//这是已经有了一个普通流,想转换为并发流
    		list.stream().parallel().forEach(System.out::println);
    		
    	}
    }
    

    7.模块化(java9及以上版本才有)

    模块化思想可以给我们带来的好处:

    1.整体一整个,文件体积过大,如果只是用到了其中的一部分内容,就会浪费

    2.精确控制package包级别的访问控制,只有导出的包,模块之外才可以访问,否则,只有模块内部自己访问

    不同的IDE有不同的创建module-info.java文件的方法

    这里讲一下Eclipse创建module-info.java的方法

    如果你想在项目上建一个模块,那就在你的项目上进行一下操作:

    右键你的项目名->找到Configure选项->点击它就会出现Create module-info.java的选项

    模块的描述信息module-info.java文件的基本内容:

    module 本模块的名称{
    	exports 导出的包名称;
    	requires 需要依赖的其他模块名称;
    }
  • 相关阅读:
    TCP协议报文段的解析
    在阿里云轻量级云服务器上安装redis
    MySQL学习(一)
    GIT学习(一)
    speed up gradle
    Android Studio plugins recommend
    Android Activity life circle brief
    install and use gradle
    Android Studio, Failed to install Intel HAXM
    Vue3 ref、reactive、toRef、toRefs的区别
  • 原文地址:https://www.cnblogs.com/luyi001/p/13406851.html
Copyright © 2011-2022 走看看