java小知识

Java小知识

匿名内部类

内部类指的是类中定义的类,匿名内部类为不需要为其命名的内部类。

匿名内部类在进行编译时做了两件事:

1、将匿名内部类编译成其一个子类

2、创建一个子类对象

通常使用匿名内部类来实现接口或者某些抽象类。

new Animal(){
    @Override
    public abstract void cry(){
        System.out.println("cat cry");
    }
}
abstract class Animal{
    public abstract void cry();
}

枚举

枚举是一种特殊类。

修饰符 enum 枚举类名{
    名称1,名称2,名称3....
        其他成员对象或方法等
}

第一行实际上是创建的实例的名称。枚举类会自动按名称创建实例,且用户不能自己创建新的实例(其构造器是私有的),仅能调用对应的实例。

public enum A{
    X,Y,Z;
    private String name;
    public String getName(){return this.name;}
    public void setName(String name){this.name=name;}
}

A a1=A.X;

抽象枚举

在枚举类中有抽象方法,详细如下,同时包含对构造器的相关知识。

public enum A{
    X(){
        @Override
        public void run(){
            System.out.println("run!");
        }
    }
    ,Y("张三"){
        @Override
        public void run(){
            System.out.println("run faster!!!");
        }
    };
    private String name;
    public abstract void run();
    //构造器不能是public的
    A(){
    }
    A(String name){
        this.name=name;
    }
}

泛型

泛型不支持基本数据类型,只能支持对象类型。即E不能为int 只能为Integer

泛型类

public class myclass<E,T>{
    public E void change(E e,T t){
        ......
        return (E) e;
    }
}

泛型方法

// 定义时
public static<T> void test (T t){
    
}
// 限定是car以及其子类
public static<T extends car> void test (T t){
    
}
//在使用泛型时使用 ? 表示一切类型
public static void test (ArrayList<?> t){
    
}
//限定
public static void test (ArrayList<? extends car> t){
    
}
//向上限定,只能是BMW以及其父类
public static void test (ArrayList<? super BMW> t){
    
}

泛型消除

泛型工作在编译阶段,对于编译成class文件就不存在泛型了

包装类

只有int =》 Integer 还有char=》Character 其他都是大写首字母。

正常需要采用如 Integer a= Integer.valuOf(12);这样的方式定义,但是Java提供了自动装箱机制可以采用 Integer a=12;同样的对类到基本类型的转化有自动拆箱机制。

包装类还提供了从字符串转化为基本类型的方法,如Double.parseDouble(str);以及Integer.valueOf(str);这些方法。

lambda表达式

用于简化匿名类代码写法。但是他只能简化函数式接口的匿名内部类。所谓函数式接口就是有且仅有一个抽象方法的接口,比如Runnable接口。对于函数式接口我们可以添加@FunctionalInterface注解。

(参数)->{
    代码
}
Arrays.sort(students,(Student s1,Student s2)->{
    return Double.Compare(s1.getHeight(),s2.getHeight());
});

对于进一步的简化规则如下:

1、参数类型可以不用写

2、如果只有一个参数,()也可以不写

3、如果方法体代码只有一行,可以省略大括号,如果选择省略那么必须同时省略分号。如果这是return语句,也必须省略return。

Arrays.sort(students,
            (s1,s2)->Double.Compare(s1.getHeight(),s2.getHeight())
);
value -> prices[value]*0.8;

方法引用

静态方法引用
类名::静态方法

若lambda表达式仅仅是调用一个静态方法,且前后参数的 形式一致 ,那么可以使用静态方法引用。

如下

Arrays.sort(students,
			(o1,o2)->Compare.ComparedByHeight(o1,o2)
);

可以写为

Arrays.sort(students,compare::ComparedByHeight);
实例方法引用
对象名::实例方法

若lambda表达式仅仅是调用一个实例方法,且前后参数的 形式一致 ,那么可以使用实例方法引用。

特定类型的方法引用
类型::方法

若lambda表达式仅是调用一个实例方法且前面参数列表中的第一个参数是方法的主调,后面的所有的参数都是作为该实例方法的入参的,可以使用特定类型的方法引用。

如下:

Array.sort(names,new Compare<String>(){
    @Override
    public int compare(String s1,String s2){
        return s1.compareToIgnoreCase(s2);
    }
})
//初步简化如下
Array.sort(names,(s1,s2)->s1.compareToIgnoreCase(s2));

在上述的表达式中通过s1这个实体调用了compareToIgnoreCase方法,而其他的参数作为新的参数传递给这个方法。因此可以简化如下

Array.sort(names,String::compareToIgnoreCase);

其中String是该方法的返回值类型。

构造器引用
类名::new

若lambda表达式仅是在创造对象,且前后参数一致,可以使用构造器引用。如下:

BeanFactory BF = (name,price)->new Car(name,price);

可以写成

BeanFactory BF = Car::new;

特殊文件

properties

是一个map集合,读取写入键值对。

读取
//创建对象
Properties pro= new Properties();
//加载文件
pro.load(new FileReader("路径"));
//得到值
String x=pro.getProperty("x");
//遍历
Set<String>k = pro.stringPropertyNames();
for(String key:k)
{
    sout.println(key,pro.getProperty(key));
}
//遍历
pro.forEach((k,v)->sout.println(k,pro.getProperty(k)));

写入
pro.setProperty("x","123");
pro.store(new FileWriter("路径"),"x had been saved");

使用setProperties方法写入对象,通过store方法保存到文件。

XML

<?xml version="1.0" encoding="UTF-8">
<!--注释内容-->
<![CDATA[任意内容]]>
&lt 替代<
&gt 替代>
&amp 替代&
&apos 替代'
&quot 替代"
XML约束文档

用于约束xml书写格式的文档。有DTD文档和Schema文档。

日志

多线程

1、继承Thread类重写run方法 。创建实体后调用start()启动

2、实现Runnable接口,实现run方法。后将实现类作为参数传递给new Thread( 参数).start()作为参数

3、实现Callable接口,可以实现返回线程执行的结果。1)重写call方法 2)将Callable对象封装成FutureTask(线程任务对象)类。3)将2得到的线程对象交给Thread对象,并使用start()启动。 4)使用FutureTask的get方法获取返回值。

值得注意的是get方法有线程锁进行同步操作,仅在对应线程结束时才能执行。

/*
ThreadMain.java
*/
Callable<String> call =new mycall(199);

FutureTask<String> f1=new FutureTask<>(call);
new Thread(f1).start();

public class mycall implements Callable<String>{
    int n;
    public mycall(int n){
        this.n=n;
    }
    
    @Override
    public String call() throw Exception{
        ......
        return ""+....;
    }
}

同步

同步代码块
synchronized(){
    ....
    ....
}

锁的选取应当是与需求相关的标识性对象,一般选取与共享资源相关的量。如实例方法:this,静态方法:类名.class。

同步方法
修饰符 synchronized 返回值类型 方法名 (参数){
    ......
        ......
}
Lock锁

lock()相当于操作系统的p操作,unlock相当于v操作。

private Lock lk = new ReentrantLock();
//实现线程的同步资源保护,同理参考操作系统可以实现线程间的异步操作,只不过注意异步时锁的应用范围。
lk.lock();
...
lk.unlock();

值得注意的是注意释放锁的位置,最好放在try 、catch、final语句中的final中,防止因为出现异常而不能释放的问题。

线程通信

对锁对象使用以下函数

wait();
notify();
notifyAll();

线程池

创建

使用固定的最大数量的线程,多余的任务将会等待空闲线程。

方法1:使用ExecutorService 的实现类ThreadPoolExecutor创建一个线程池对象。

方法2:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。

使用ThreadPoolExecutor
ThreadPoolExecutor(int corePoolSize,int maximumPoolsize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnale> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)

参数的含义分别为:

corePoolSize 指定核心线程的数量(长久复用的线程);maximumPoolsize最大线程数量(可以创建额外的临时线程);keepAliveTime临时线程的存活时间;unit 上一个参数的时间单位;workQueue任务队列(缓存任务);threadFactory线程工厂,用于创建线程;handler指定线程池的任务拒绝策略(任务队列都满了的时候,如果有新线程怎么处理);

ExecutorService pool = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,new ArrayBlockingQueue<>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
注意事项

临时线程何时创建:核心线程都忙且任务队列满了,且还可以创建临时线程的时候创建。

何时拒绝:都满且无法创建。

使用Executors

是一个线程池的工具类,提供很多静态方法返回不同特点的线程池对象。

几个方法如下:

//创建固定线程数量的线程池,若某线程出现异常,会创建一个新线程
public static ExecutorService newFixedThreadPool(int nthreads);
//上一个方法参数为1
public static ExecutorService newSingleThreadExecutor();
//随任务数量增加而增加线程,空闲60s则回收
public static ExecutorService newCachedThreadPool();
//固定核心线程,用于定时调度
public static ScheduledExecutorService newScheduledThreadPool(int corePoolsize);
    

创建:

ExecutorService pool = new newFixedThreadPool(3);
使用

ExecutorService 的常用方法:

//执行无返回值的任务
void execute(Runnable command);
//执行有返回值的对象
Future <T> submit(Callable<T> task);
//等前面的任务执行完后关闭线程池
void shutdown();
//立即关闭线程池,返回未执行完毕的线程
list<Runnable> shutdownNow();
核心线程数量的配置技巧

计算密集型的任务: 核心线程数量= CPU核数+1

IO密集型的任务: 核心线程数=CPU核数*2

注意点

大型并发系统环境中使用Executors可能出现系统风险,比如FixedThreadPool没有对线程的请求队列长度做限制,可能造成内存溢出

网络通信

InetAddress

代表在java中封装的IP地址

常用的方法:

public static InetAddress getLocalHost();
//根据ip地址或者域名返回一个InetAddress对象
public static InetAddress getByName(String host);
//获取该IP对应的主机名
public String getHostName();
//获取IP地址信息
public String getHostAddress();
//尝试PING,ms为单位
public boolean isReachable(int timeout);

示例如下:

InetAddress ip1 = InetAddress.getByName("www.baidu.com");
sout.println(ip1.getHostName());
sout.println(ip1.getHostAddress());
sout.println(ip1.isReachable(6000));

UDP通信

Java提供了一个java.net.DatagramSocket类实现UDP通信。

构造器如下

//创建客户端socket对象,系统分配端口号
public DatagramSocket();
//创建服务端socket对象指定端口号
public DatagramSocket(int port);

方法如下

//发送数据包
public void send(DatagramPacket dp);
//接受数据包
public void receive(DatagramPacket p);

DatagramPacket的构造器如下:

//创建发送数据包,参数为数据,数据长度,服务IP,端口号
public DatagramPacket(byte[] buf,int length,InetAddress address,int port);
//创建接收数据包
public DatagramPacket(byte[] buf,int length);

方法

//获取数据包实际接收的字节个数
public int getLength();

客户端示例:

byte[] bytes = "111".getBytes();
DatagramPacket packet = new DatagramPacket(bytes,bytes.length,InetAddress.getByName("www.mxselfweb.cn"),8080);
//
DatagramSocket socket = new DatagramSocket();
socket.send(packet);
socket.close();

服务端示例:

byte[] buffer = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
//
DatagramSocket socket = new DatagramSocket();
socket.receive(packet);
String rs = new String(buffer,0,packet.getLength());
socket.close();

TCP通信

Java提供了一个java.net.Socket类来实现TCP通信中的客户端。

客户端构造器:

//根据指定的服务器IP、端口号请求与服务端建立连接,连接通过则获得客户端Socket
public Socket(String host ,int port);

客户端方法:

//获取字节输出流对象
public OutputStream getOutputStream();
//获取字节输入流对象
public InputStream getInputStream();

服务端是通过java.net.ServiceSocket类实现的。

服务端构造器:

//为服务端注册端口
public ServiceSocket(int port);

服务端方法:

//阻塞客户端请求,一旦成功连接返回服务端这边的socket
public Socket accept();

客户端示例:

//建立连接
Socket socket = new Socket("127.0.0.1",8080);
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while(1)
{
    sout.println("inout:");
    String msg = sc.nextLine();
    dos.writeUTF(msg);
    dos.flush();
}
dos.close();
//释放连接
socket.close();

服务端示例:

ServiceSocket serviceSocket = new ServiceSocket(8080);
//主线程负责接收连接并对每一个连接创建子线程
while(1)
{
    Socket socket = serviceSocket.accept();
    new ServerReaderThread().start();
}

sout.prinln(socket.getRemoteSocketAddress());

服务端线程接口

public class ServerReaderThread implements Runnable{
    private Socket socket;
    public ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    public void run(){
  	//从socket通信管道中得到一个字节输入流
    InputStream is = socket.getInputStream();
    //原始字节输入包装成数据输入流
    DataInputStream dis =new DataInputStream(is);
    while(1)
    {
        try{	
            String rs = dis.readUTF();
            sout.println(rs);
        }
        catch(Exception e){
 			sout.println( socket.getRemoteSocketAddress() + "离线了");
            dis.close();
            socket.close();
            break;
        }
    }
} 

Junit

编写测试类,在测试方法前加上@Test注解

使用断言处理异常情况,Assert.assertEqueals(“方法内部有bug”,4,index);对于index不等于4的情况会进行断言。

可以对测试方法单独运行,也可以运行测试类,同时可以点击项目运行all_test

其他注解:

@Before 修饰实例方法,将会在每个测试方法前执行

@After 实例,在之后

@BeforeClass 修饰静态方法,在之前

@AfterClass 静态,在之后

反射

在java.lang.reflect包下。允许访问已知加载类的字段、方法和构造函数的信息并操作。换言之就是加载类,并允许以变成的方式解剖类中的各种成分(成员遍历、方法、构造器等)。

1、加载类,获取类的字节码:class对象

2、获取类的构造器:Constructor对象

3、获取类的成员变量:Field对象

4、获取类的成员方法:Method对象

加载类

class c1 = 类名.class;
//调用Class提供方法,package参数需要填写全类名
public static Class forName(String package);
//Object 提供的方法
public Class get Class();
Class c3 = 对象.getClass(); 

获取类的构造器

//获取全部构造器,只有public 的可以拿到
Constructor<?>[] getConstructors();
//获取全部构造器,存在就能拿到
Constructor<?>[] getDeclaredConstructors();
//获取某个构造器,只有public 的可以拿到
//e.g: c.getConstructor(String.class,int.class);
Constructor<T> getConstructor(Class<?>...parameterTypes);
//获取某个构造器,存在就能拿到
Constructor<T> getDeclaredConstructor(Class<?>...parameterTypes);

创建实例

//调用无参构造器创建实例
Constructor constructor1 = c1.getDeclaredConstructor();
constructor1.setAccessible(true)//访问权限,对于private的构造器也可以调用
Object o1 = constructor1.newInstance();
//调用有参构造器
Constructor constructor2 = c1.getDeclaredConstructor( String.class,int.class);
constructor2.setAccessible(true)
Object o2 = constructor2.newInstance("aa",2 );

获取类的成员变量

//参考获取构造器的注解
public Field[] getFields();
//
public Field[] getDeclaredFields();
//
public Field getField(String name);
//
public Field getDeclaredField(String name);

为成员变量进行赋值和取值

//取出该类的name成员对象
Field fName = c1.getDeclaredField("name");
//权限
fName.setAccessible(true);
//为o1实例的name成员赋值
fName.set(o1,"加菲猫");
//取o1的name值
Object nameO1 = fName.get(o1);

获取类的成员方法

//参考获取构造器的注解
public Method[] getMethods();
//
public Method[] getDeclaredMethods();
//
public Method getMethod(String name, class<?>...parameterTypes);
//
public Method getDeclaredMethod(String name,class<?>...parameterTypes);

使用类的成员方法

//取出该类的name成员对象
Method mRun = c1.getDeclaredMethod("run");
Method mEat = c1.getDeclaredMethod("eat", String.class, String.class);
sout.(mRun.getName()+" - "+mRun.getParameterCount() + " - " + mRun.getReturnType());
//权限
mRun.setAccessible(true);
//调用无参方法
object rRun = mRun.invoke(o1);
//调用有参方法
object rEat = mRun.invoke(o1,"猫罐头","矿泉水");

注解

让其他程序根据注解信息决定如何执行该程序。

原理

实际上是继承了Annotation的接口,其中的属性其实都是一些抽象方法,使用@的时候实际上是创建了一个实现类对象实现该注解以及Annotation接口。

自定义注解

public @interface 注解名称{
    pulbic 属性类型 属性名() default 默认值;
}

特殊属性名:value

如果只有一个value属性,则可以不写。

示例

public @interface m1{
    public int age() default 23;
    String value();//特殊属性
    
}

//使用时如下:
@m1(age=100,value="abc")
@m1("abc")//只有一个特殊属性未指定可以不写value

元注解

修饰注解的注解。

@Target

用于声明被修饰的注解只能在那些位置使用

1、TYPE,类、接口

2、FIELD,成员变量

3、METHOD,成员方法

4、PARAMETER,方法参数

5、CONSTRUCTOR,构造器

6、LOCAL_VARIABLE,局部变量

也可以用,隔开使得可以多种类型的修饰如下:

@Target(ElementType.TYPE,ElementType.METHOD)

@Retention

用于声明注解的保留周期

1、SOURSE;值作用在源码阶段,字节码文件中不存在。

2、CLASS(默认值);保留到字节码文件阶段,运行阶段不存在。

3、RUNTIME(开发常用);一直保留到运行阶段。

示例如下:

@Retention(RententionPolicy.RUNTIME)

注解的解析

判断类、方法、成员变量上是否存在注解,并把注解的内容解析出来。

如何解析:

​ 指导思想:解析谁上面的注解,就应该先拿到谁。比如解析类上的,就先获取Class对象。

​ Class、Method、Field、Constructor都实现了AnnotatedElement接口,它们都有解析注解的能力。

AnnotatedElement提供的解析注解的方法:

//获取当前对象上的注解
public Annotation[] get DeclaredAnnotations();
//获取指定的注解对象
public T getDeclaredAnnotation(Class<T> annotationClass);
//判断当前对象上是否存在某个注解
public boolean isAnnotationPresent(Class<Annotation> annotationClass);

使用示例如下:

//得到解析的对象,解析test1方法前的注解
Class c=Demo.class;
Method m = c.getDeclaredMethod("test1");
//判断是否有这个注解
if(m.isAnnotationPresent(Mytest.class)){
    Mytest myTe = (Mytest) m.getDeclaredAnnotation(Mytest.class);
    sout(myTe.value());//输出注解的value属性
}

模拟案例

模拟Junit框架,只要加了MyTest注解就会触发该方法执行。

1、定义一个自定义注解,只注解方法,一直存活。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest{
    
}

2、定义若干个方法,部分方法加上@MyTest注解修饰

public class AnnotationTest{
    @MyTest
    public void test1(){
		sout("test1");
    }

    public void test2(){
		sout("test2");
    }
}

3、模拟一个junit程序,可以触发加了@MyTest注解的方法执行

public static void main(String[] args){
    //程序,启动!
    AnnotationTest a = new AnnotationTest();
    Class c = AnnotationTest.class;
    Method[] methods = c.getDeclaredMethod();
    for(auto method:methods){
        if(method.isAnnotationPresent(MyTest.class)){
            method.invoke(a)
        }
    }
}

代理

在程序运行期,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。

Proxy类的方法如下:

static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);

三个参数分别如下:

ClassLoader (类加载器)

从硬盘把字节码加载到JVM内存中,需要一个对象去完成这个操作,这个对象就叫ClassLoader (类加载器)。通常使用目标类的字节码文件调用getClassLoader()方法可得到。

接口数组:

指定生成的代理长什么样子,有那些方法。

指定目标类的所有接口的字节码对象的数组,通常使用目标类的字节码文件调用getinterfaces()方法可得到。

调用处理器:

说明代理对象干什么事情。一般使用接口的匿名内部类对象重写invoke方法。

代理对象的几乎所有方法都会调用InvocationHandler的invoke()方法(回调方法)

invoke()方法中参数的含义:

proxy:

​ 传入当前代理对象。即调用它的主体。

​ 就是代理类对象的一个引用也就是Proxy.newProxyInstance()方法的返回值;此引用几乎不会用到。
method:

​ 传入当前代理使用的方法。

​ 对应的就是触发invoke执行的方法的Method对象。假如我们调用了Xxx方法,该方法触发了invoke执行,那么method就是Xxx方法对应的反射对象Method。
args:

​ 代理对象调用方法时传入的实际参数。

示例:

public class ProxyUtil{
    public static Star createProxy(BigStar bigStar)
    {
        Star starProxy = (Star) Proxy.newProxyInstance(
            ProxyUtil.class.getCkassLoader(),
            bigStar.getClass().getInterfaces(),
            new InvocationHandler() {
            //回调方法
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if(method.getName().equals("sing")){
                    sout("准备唱歌");
                }
                else if(method.getName().equals("dance")){
                    sout("准备跳舞");
                }
                
                
                //使用反射以创建时实际传入的主体bigStar调用对应的method方法
                Object ans = method.invoke(bigStar,args);
                
                
                if(method.getName().equals("sing")){
                    sout("唱歌结束");
                }
                else if(method.getName().equals("dance")){
                    sout("跳舞结束");
                }
                return ans;
            }
        });
        return starProxy;
    }
}

//main
BigStar s = new BigStar("Jay zhou");
Star starProxy = ProxyUtil.createProxy(s);
String ou = starProxy.sing("一路向北");
sout(ou);

上述程序的执行过程如下:

1、创建主体s

2、创建对主体s的代理starProxy

3、starProxy调用invoke方法,并将自己作为proxy参数、sing作为method参数,“一路向北”作为args参数传入。

4、invoke方法调用method.invoke(bigStar,args);实际上是采用反射的方式执行bigStar对象的method方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值