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 需要依赖的其他模块名称;
}
原文地址:https://www.cnblogs.com/luyi001/p/13406851.html